diff --git a/content/posts/notes_general/notes_3dprinter.md b/content/posts/notes_general/notes_3dprinter.md new file mode 100644 index 0000000..456dfd1 --- /dev/null +++ b/content/posts/notes_general/notes_3dprinter.md @@ -0,0 +1,148 @@ +--- +weight: 4 +title: "Apuntes de impresión 3D" +date: 2022-08-20T12:04:30+0200 +draft: false +hiddenFromHomePage: true +summary: "Apuntes incompletos de impresión 3D" +categories: +- notes +tags: +- 3dprinter +--- + + +## Instalación del software en nuestro ordenador + +### Instalación de octoprint en docker + +___Pronterface___ se ha vuelto imposible de instalar para mi, así que vamos a probar ___Octoprint___ dockerizado, así ya lo tenemos controlado para instalarlo en una _Orangepi Zero_ en la propia impresora. + +El fichero `docker-compose.yml` para el Octoprint (cortesía de Caligari): + +```yml +version: '2.4' + +services: + octoprint: + image: octoprint/octoprint + container_name: octoprint + restart: unless-stopped + network_mode: bridge + ports: + - "80:80" + devices: + - "/dev/bus/usb" + volumes: + - "./data:/octoprint" + - "/run/udev:/run/udev:ro" + device_cgroup_rules: + - "c 188:* rmw" # USB to serial + - "c 81:* rmw" # USB webcams + # uncomment the lines below to ensure camera streaming is enabled when + # you add a video device + #environment: + # - ENABLE_MJPG_STREAMER=true + + #### + # uncomment if you wish to edit the configuration files of octoprint + # refer to docs on configuration editing for more information + #### + #config-editor: + # image: linuxserver/code-server + # ports: + # - 8443:8443 + # depends_on: + # - octoprint + # restart: unless-stopped + # environment: + # - PUID=0 + # - PGID=0 + # - TZ=America/Chicago + # volumes: + # - octoprint:/octoprint + +``` + + +Tenemos que adaptar la parte del mapeo del puerto USB para usarlo en nuestro ordenador. + +A continuación algunas notas de como se puede hacer el mapeo del puerto USB en nuestro ordenador. + +``` + + + +DSM docker UI application does not have option to set "device". + +But you still can do this by following steps: + + create "octoprint" container with all settings you need (volumes, ports etc) + select "Settings" -> "Export" and export docker container configuration JSON to local computer + open exported JSON in any editor + find string + +"devices" : null, + +and replace it with + + "devices" : [ + { + "pathOnHost": "/dev/ttyUSB0", + "PathInContainer": "/dev/ttyUSB0", + "CgroupPermissions": "rwm" + } + ], + + Import JSON file back to DSM docker UI + +P.S. Also I found that my "/dev/ttyUSB0" has "crw-------" permission, and can't be accessed inside docker by octoprint application. So I have to manually set to "crw-rw-rw-" with + +chmod a+rw /dev/ttyUSB0 + + +----- +also + + + + Suggest adding instructions to to identify the USB port being used and then passing this device into the container. + + With the pi connected to the printer via usb + ls /dev | grep tty + + This should return a long list of 'tty' The one we need is found at the end of the list and is probably + ttyUSB0 if no other USB devices are plugged in. + + In order to use this ttyUSB0 add this to your docker run command + + docker run -device /dev/ttyUSB0:/dev/ttyUSB0 ..... + + Now octoprint can detect the the USB port and connect to the printer. + +Hi +i installed everything and it works. unfortunately I can't enable the usb and launching the command (pi4): docker: invalid reference format: repository name must be lowercase. +See 'docker run --help'. + +some advice? + +``` + + + +## Partes de repuesto + +- [Engranaje de poleas](https://www.aliexpress.com/item/32817328238.html) +- [Pantalla de control](https://www.aliexpress.com/item/1005003952110687.html) +- [Husillos de lujo](https://www.aliexpress.com/item/1005004176770736.html) +- [Husillos normales](https://www.aliexpress.com/item/32818374370.html) +- https://www.aliexpress.com/item/1005003057778867.html +- https://www.aliexpress.com/item/1005003065012440.html +- https://www.aliexpress.com/item/1005004448072856.html + +- [Porta bobinas](https://www.aliexpress.com/item/1005003873495589.html) +- [Limpiador de filamento](https://www.aliexpress.com/item/1005001431768691.html) +- [Patas antivibración](https://www.aliexpress.com/item/4000298849640.html) +- [Caja para secar el filamento](https://www.aliexpress.com/item/1005004415842339.html) (es muy mejorable) +- [Tornillos para la cama](https://www.aliexpress.com/item/4001155732510.html) +- [Conjunto de poleas sencillo](https://www.aliexpress.com/item/4001148474252.html) diff --git a/content/posts/notes_general/notes_SDR.md b/content/posts/notes_general/notes_SDR.md new file mode 100644 index 0000000..c61e03b --- /dev/null +++ b/content/posts/notes_general/notes_SDR.md @@ -0,0 +1,96 @@ +--- +weight: 4 +title: "Apuntes de SDR" +date: 2020-05-29 +draft: false +summary: "Notas sobre SDR y montaje de Open Plotter en Raspberry Pi" +categories: + - notes +tags: + - raspi + - sdr + - barco +--- + +{{< admonition type=warning title="Work in progress" open=true >}} + +No está completo. + +{{< /admonition >}} + + + +## Referencias + +- https://www.youtube.com/watch?v=N8zxaMjA0qA +- https://youtu.be/oqOyNPMbu5o +- [A good quickstart guide for RTL-SDR Linux Users](https://www.rtl-sdr.com/a-good-quickstart-guide-for-rtl-sdr-linux-users/) +https://ranous.wordpress.com/rtl-sdr4linux/ +- [Skywavelinux](https://skywavelinux.com/) + +## Inhibir drivers DVB en udev rules para nuestro dispositivo SDR + +Si echamos un vistazo al log del sistema en `/var/log/syslog` mientras conectamos el dispositivo SDR-RTL al puerto usb, obtenemos: + +```bash +tail -f /var/log/syslog +. +. +. +Aug 25 16:50:19 achernar kernel: [51499.253763] usb 1-3: new high-speed USB device number 12 using xhci_hcd +Aug 25 16:50:19 achernar kernel: [51499.415521] usb 1-3: New USB device found, idVendor=0bda, idProduct=2838, bcdDevice= 1.00 +Aug 25 16:50:19 achernar kernel: [51499.415534] usb 1-3: New USB device strings: Mfr=1, Product=2, SerialNumber=3 +Aug 25 16:50:19 achernar kernel: [51499.415541] usb 1-3: Product: RTL2838UHIDIR +Aug 25 16:50:19 achernar kernel: [51499.415546] usb 1-3: Manufacturer: Realtek +Aug 25 16:50:19 achernar kernel: [51499.415550] usb 1-3: SerialNumber: 00000001 +Aug 25 16:50:19 achernar kernel: [51499.426274] usb 1-3: dvb_usb_v2: found a 'Realtek RTL2832U reference design' in warm state +Aug 25 16:50:19 achernar kernel: [51499.486288] usb 1-3: dvb_usb_v2: will pass the complete MPEG2 transport stream to the software demuxer +Aug 25 16:50:19 achernar kernel: [51499.486303] dvbdev: DVB: registering new adapter (Realtek RTL2832U reference design) +Aug 25 16:50:19 achernar kernel: [51499.486311] usb 1-3: media controller created +Aug 25 16:50:19 achernar kernel: [51499.487207] dvbdev: dvb_create_media_en tity: media entity 'dvb-demux' registered. +. +. +. +``` + +De este log podemos sacar el `idVendor` y el `idProduct`, además vemos que linux le está asignando un driver `dvd_usb` (_Digital Video Broadcast_) o sea que lo identifica como un sintonizador de televisión y no queremos que eso pase. + +De momento la única solución parece ser poner el driver en la lista negra. Para ello creamos un fichero `/etc/modprobe.d/blacklist-rtl.conf` con el siguiente contenido: + +```bash +blacklist dvb_usb_rtl28xxu +``` + +Tras reiniciar el sistema veremos que al introducir el dispositivo usb, no se le asigna el _driver_ de televisión. + + +Instalamos los drivers (yo uso un pincho USB RTL-SDR v3) Los drivers disponibles en mi Linux Mint tienen la misma versión que en el github del proyecto. + +```bash +sudo apt install rtl-sdr +``` + + + + +Para solucionarlo podemos crear un fichero `/etc/udev/rules/99-sdl-rtl.rule` con el siguiente contenido: + +```bash +# for arduino brand, stop ModemManager grabbing port +ATTRS{idVendor}=="2a03", ENV{ID_MM_DEVICE_IGNORE}="1" + +``` + + + +SUBSYSTEMS=="usb", ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="2838", MODE:="0666" + +idVendor=0bda, idProduct=2838 + + +Aug 24 18:33:45 achernar kernel: [26699.444347] usb 1-3: new high-speed USB device number 8 using xhci_hcd +Aug 24 18:33:45 achernar kernel: [26699.608542] usb 1-3: New USB device found, idVendor=0bda, idProduct=2838, bcdDevice= 1.00 +Aug 24 18:33:45 achernar kernel: [26699.608557] usb 1-3: New USB device strings: Mfr=1, Product=2, SerialNumber=3 +Aug 24 18:33:45 achernar kernel: [26699.608564] usb 1-3: Product: RTL2838UHIDIR +Aug 24 18:33:45 achernar kernel: [26699.608569] usb 1-3: Manufacturer: Realtek +Aug 24 18:33:45 achernar kernel: [26699.608574] usb 1-3: SerialNumber: 00000001 diff --git a/content/posts/notes_general/notes_aikido.md b/content/posts/notes_general/notes_aikido.md new file mode 100644 index 0000000..b88017d --- /dev/null +++ b/content/posts/notes_general/notes_aikido.md @@ -0,0 +1,294 @@ +--- +weight: 4 +title: "Apuntes de Aikido" +date: 2020-09-20T11:37:21+0200 +draft: true +summary: "Apuntes de Aikido" +hiddenFromHomePage: true +categories: +- notes +tags: +- aikido +--- + +## Programa Aikido + +### Kyu + +#### 6TO KYU (40 Días) + + + +- Tai Sabaki: + - Hidari / Migi Hanmi (Pie izq. adelantado / Pie dch. adelantado) + - Irimi (Tsugi-ashi, Ayumi-Ashi) + - Kaiten + - Tenkan + - Irimi-tenkan + - Tenshin + - Irimi-Tenshin + +- Shikko: + - Shikko + - Shikko Tai Sabaki + +- Ukemis: + - Mae Kaiten + - Ushiro Kaiten + - Mae Yoko Kaiten, + - Uchiro Yoko Kaiten + - Uchiro Mae Kaiten + - Uchiro Mae Yoko Kaiten + - Uchiro Otoshi. + +- Técnicas: + - Katate Dori Ai-hanmi Ikkyo (Omote & Ura) + - Katate Dori Ai-hanmi Iriminage (Omote & Ura) + - Katate Dori Ai-hanmi Shihonage (Omote & Ura) + - Katate Dori Ai-hanmi Kotegaeshi (Omote & Ura) + - Katate Dori Ai-hanmi Nikyo (Omote & Ura) + + +#### 5TO KYU (60 Días) + +##### Conocimientos + - Kokyuho + - Shikko + +##### Tachi Waza + +- Shomen uchi Ikkyo (Omote & Ura) +- Shomen uchi Nikyo (Omote & Ura) +- Shomen uchi Kotegaeshi (Omote & Ura) +- Chudan Tsuki Iriminage (Omote & Ura) +- Chudan Tsuki Kotegaeshi (Omote & Ura) +- Katatedori Shihonage (Omote - Ura) +- Katatedori Tenchinage +- Katatedori Ikkyo (Omote - Ura) +- Katatedori Udekimenage (Omote - Ura) +- Katatedori Kokyunage + + + +##### Suwari Waza + +- Shomen uchi Ikkyo (Omote - Ura) +- Katadori Ikkyo (Omote - Ura) + +#### 4TO KYU (90 Días) + +##### Tachi Waza + +- Shomen uchi Sankyo (Omote - Ura) +- Shomen uchi Uchikaiten Sankyo +- Yokomen uchi Shihonage (Omote - Ura) +- Yokomen uchi Iriminage (Omote - Ura) +- Yokomen uchi Tenchinage +- Yokomen uchi Kotegaeshi +- Yokomen uchi Udekimenage +- Chundan tsuki Udekimenage +- Chundan tsuki Hijikimeosae +- Jodan tsuki Ikkyo (Omote - Ura) +- Ryotedori Shihonage (Omote - Ura) +- Ryotedori Tenchinage +- Ryotedori Udekimenage +- Ryotedori Ikkyo (Omote - Ura) +- Katatedori Nikyo (Omote - Ura) +- Katateryotedori Kotegaeshi + +##### Suwari Waza + +- Shomen uchi Nikyo (Omote - Ura) +- Katadori Nikyo (Omote - Ura) +- Ryotedori Kokyuho + + + +#### 3ER KYU (100 Días) + +- Shomen uchi Sankyo (Omote & Ura) +- Shomen uchi Yonkyo (Omote & Ura) +- Yokomen uchi Iriminage (Omote & Ura) (3 formas) +- Yokomen uchi Kotegaeshi (Omote & Ura) (3 formas) +- Chudan Tsuki Kaiten nage uchi (Omote & Ura) +- Chudan Tsuki Kaiten nage soto (Omote & Ura) +- Ushiro Ryo kata Dori Sankyo (Omote & Ura) +- Morote Dori Iriminage (Omote & Ura) (3 formas) +- Morote dori Jujinage (Irimi, Tenkan) +- Suwari waza Shomen uchi Iriminage (Omote & Ura) +- Suwari waza Shomen uchi Nikkyo (Omote & Ura) +- Suwari waza Shomen uchi Sankyo (Omote & Ura) +- Hanmi handachi Katate Dori Shihonage (Omote & Ura) +- Hanmi handachi Katate Dori Kaiten nage Uchi (Omote & Ura) +- Hanmi handachi Katate Dori Kaiten nage Soto (Omote & Ura) + + BUKI WAZA : + +- Zengo, Shiho, Happo Giri (ken) +- Ki musubi no tachi +- Aiki jo no suburi (20) + + +#### 2DO KYU (200 Días) + +- Shomen uchi Shihonage (Omote & Ura) +- Shomen uchi Kaiten nage Uchi (Omote & Ura) +- Shomen uchi Kaiten nage Soto (Omote & Ura) +- Yokomen uchi Gokyo (Omote & Ura) +- Ushiro Ryo tekubi Dori Shihonage (Omote & Ura) +- Ushiro Ryo Tekubi Dori Jujinage +- Ushiro Kubishime Koshinage (2 Formas) +- Morote Dori Nikkyo (Omote & Ura) (Ai-hanmi, Gyaku-hanmi) +- Hanmi handanchi Shomen uchi Iriminage (Omote & Ura) +- Hanmi handanchi Katate dori Gyaku Hanmi Nikkyo (Omote & Ura) +- Hanmi handanchi Yokomen uchi Kotegaeshi (Omote & Ura) +- Futari Dori: 2 Personas +- Randori: 3 Personas + +BUKI WAZA: + +- Zengo, Shiho, Happo Giri (Jo) +- Ku no Kata (9) +- Aiki Ken Buki Dori (5 variaciones) +- Aiki Jo Buki dori (5 variaciones) +- Tanto Dori (5) + +#### 1ER KYU (300 Días) + +Todos los requerimientos de los Kyu Anteriores. + +JIYU WAZA: + +- Kata Dori menuchi – 5 técnicas +- Yokomen uchi – 5 técnicas +- Morote Dori – 5 técnicas +- Shomen uchi – 5 técnicas +- Tsuki – 5 técnicas +- Ryote Dori – 5 técnicas +- Uchiro Waza – 5 técnicas +- Koshinage – 5 técnicas +- Hanmi handachi Ushiro Waza – 5 técnicas +- Tanto Dori (5) +- Randori: 5 Personas + +BUKI WAZA: + +- Ju san Jo no kata (13) +- San Su ichi jo no kata (31) +- Buki Dori (2 ukes) + +### Técnicas Kyu por ataques + +#### Ai-hammi Katatedori + +- (6 Kyu) Iriminage (Omote-Ura) +- (6 Kyu) Kotegaeshi +- (6 Kyu) Ikkyo (Omote-Ura) +- (6 Kyu) Nikkyo (Omote-Ura) +- (6 Kyu) Sankyo (Omote-Ura) +- (6 Kyu) Shihonage (Omote-Ura) +- (6 Kyu) Uchikaitennage + +#### Katatedori (Gyaky-hammi) + +- (5 Kyu) Shihonage (Omote-Ura) +- (5 Kyu) Tenchinage (Omote-Ura) +- (5 Kyu) Ikkyo (Omote-Ura) +- (5 Kyu) Udekiminage (Omote-Ura) +- (5 Kyu) Kokyunage +- (4 Kyu) Nikyo (Omote-Ura) +- (3 Kyu) Uchikaitennage +- (3 Kyu) Sankyo (Omote-Ura) +- (3 Kyu) Yonkyo (Omote-Ura) + +##### Ushiro Waza + +- (2 Kyu) Katatedori Kubishime - Ikkyo +- (1 Kyu) Katatedori Kubishime - Sankyo +- (1 Kyu) Katatedori Kubishime - Nikyo + +##### Hamihandachi Waza + +- Shihonage (Omote-Ura) +- Uchikaitennage +- Ikkyo (Omote-Ura) + +#### Shomen Uchi + +#### Yokomen Uchi + +### DAN + +#### SHODAN (300 Días) + +- Todos los requerimientos de 1er Kyu +- Henka Waza +- Aiki ken Dori, +- Aiki Jo Dori (jo uke, jo nage) +- 2 Randori de 5 Atacantes. +- Kumi tachi (5) Básico + +#### NIDAN (600 Días) + +- Todos los requerimientos de Shodan. +- Aiki Jo Dori 2 atacantes +- Aiki Ken Dori 2 atacantes +- Kaeshi Waza +- Randori 5 atacantes +- Kumi tachi (5) y variaciones, (Ken no Ri, Tai no Ri) +- Kumi Jo (10) + +#### SANDAN (700 Días) + +Sujeto a exámen o promoción según el examinador. Debe asistir a dos seminarios por año. + +Todos los requerimientos de Nidan + Buki Waza: + +- Kumi Jo (Variaciones) + +#### YONDAN (4 años después del Sandan) + +Sujeto a exámen o promoción según el examinador. Debe asistir a dos seminarios por año. + +- Ken tai Jo (7) (Variaciones) diff --git a/content/posts/notes_general/notes_docker.md b/content/posts/notes_general/notes_docker.md new file mode 100644 index 0000000..c7544dd --- /dev/null +++ b/content/posts/notes_general/notes_docker.md @@ -0,0 +1,1770 @@ +--- +weight: 4 +title: "Apuntes de Docker" +date: 2021-01-23T16:01:50+0100 +draft: false +summary: "Apuntes de docker" +categories: + - notes +tags: + - docker + - traefik +--- + +{{< image src="/images/docker_logo_wide.jpg" >}} + +{{< admonition type=warning title="Work in progress" open=true >}} +Estos apuntes no están completos, (ni de lejos) +{{< /admonition >}} + +## Conceptos + +### El software + +El software de Docker implementa el estándar OCI (_Open Container +Initiative_) tan fielmente como les es posible. + +La OCI especifica: + +* Imágenes +* _Runtime containers_ + +runc + +: Es la implementación para _Docker_ de la especificación de + _runtime container_ de la OCI. + + La misión de _runc_ es crear contenedores, puede funcionar por si + solo via CLI (es un binario) pero funciona a muy bajo nivel. + +containerd + +: Es un demonio. Se encarga del ciclo de vida de los contenedores: + arrancarlos, pararlos, pausarlos, borrarlos. + + Se encarga también de otras cosas como _pulls_, _volumes_ y + _networks_. Esta funcionalidad adicional es modular y opcional y + se introdujo para facilitar el uso de _containerd_ en otros + proyectos como por ejemplo _Kubernetes_. + +shim + +: _shim_ se encarga de dejar conectados los containers con + _containerd_ una vez que _runc_ termina su misión y muere. + +### _images_ + +Puede pensarse que una imagen es como un contenedor parado. De hecho +puedes parar un contenedor y construir una imagen a partir de el. Se +dice que las imágenes son objetos de _build-time_ y los contenedores +son objetos de _run-time_ + +Las imágenes se pueden almacenar centralizadas en los _image +registries_. Por ejemplo _dockerhub_ es un _image registry_, +seguramente el más popular, pero tu podrías tener tu propio _image +registry_. + +{{< admonition type=warning title="Seguridad e Imágenes" state=open >}} + +Mucho ojo con esto, por que en el [Docker Store](https://hub.docker.com/) nos podemos encontrar todo tipo de imágenes, al fin y al cabo cualquiera puede publicar sus imagenes. No debemos confiar automáticamente en cualquier imagen. Ten cuidado con tus fuentes. + +__¡No te fies de las etiquetas!__ Son solo eso, etiquetas. Por ejemplo _latest_ se supone que apunta a la última versión de un contenedor pero no hay ningún mecanismo que lo asegure, _alguien_ tiene que ocuparse de que la etiqueta apunte al contenedor correcto. + +{{< /admonition >}} + +Las __imágenes oficiales__ suelen residir en un repo de primer nivel dentro de _dockerhub_ y tendrán una url como esta, por ejemplo: + + +### _containers_ + +Como dijimos arriba, un _container_ es una instancia _runtime_ de una +imagen. A partir de una imagen podemos crear varios _containers_. + +Un contenedor es muy parecido a una máquina virtual clásica (como las +de _Virtual Box_ por ejemplo). La principal diferencia es que son +mucho más ligeros y rápidos, ya que comparten _kernel_ con la máquina +_host_. Además suelen basarse en imágenes lo más ligeras posible que +contienen solo lo imprescindible para que el contenedor cumpla su +función. + +### _volumes_ + +### _networks_ + + +## cheatsheet + +### Imágenes + +| Comando | Efectos | +|:--------------------|:---------------------------------------| +| docker images | Lista las imágenes disponibles | +| docker ps | Lista los container en ejecución | +| docker ps -a | Lista todos los container | +| docker build | construye imagen desde dockerfile | +| docker history | Muestra la hist. es decir como se hizo | +| docker inspect | Detalles de la imagen (p.ej. ip) | +| docker images | Lista todas las imágenes | +| docker images -a | lista tb las imágenes intermedias | +| docker images -q | lista solo identificativos | +| docker system prune | Borra todas las imágenes no usadas | +| docker pull | descarga una imagen | +| docker push | sube una imagen a la nube | +| docker image rm | Borra imágenes, alias `docker rmi` | + +### Contenedores + +| Comando | Efectos | +|:------------------------|:--------------------------------------| +| docker run | Ejecuta un contenedor | +| docker run -d | detached | +| docker run -name | para pasarlo por nombre | +| docker container rename | para renombrar un contenedor | +| docker ps | Para ver cont. ejecutandose | +| docker ps -a | Para verlos todos | +| docker stop | Para parar uno, `SIGTERM` + `SIGKILL` | +| docker start | reiniciar un cont. | +| docker restart | stop + start | +| docker kill | `SIGKILL` | +| docker pause | lo pone en pausa | +| docker unpause | lo saca de la pausa | +| docker cp | copiar ficheros a y desde cont. | +| docker exec -it | ejecutar y abrir terminal | +| docker top | ver estadísticas del cont. | +| docker stats | para ver más stats. `--no-stream` | +| docker --rm | borrar cont. al sali | +| docker container prune | borrar todos los cont. parados | + +__Ejemplos__: + +- Lanzar un alpine y abrir un terminal contra el: + + ```bash + docker run -it --rm alpine /bin/ash + ``` + +- Abrir un terminal contra un contenedor que ya está corriendo: + + ```bash + docker exec -it bash + ``` + +### Volumes + +| Comando | Efectos | +|:------------------------|:--------------------------------------| +| docker volume prune | Borrar todos los `volume` no usados | + +### Dockerhub + +| Comando | Efectos | +|:---------------------|:-------------------------| +| docker commit | crea imagen desde cont. | +| docker login | | +| docker push | | +| docker search ubuntu | busca imágenes en el hub | + + +### docker inspect ### + +```shell +docker inspect --format '{{ .NetworkSettings.IPAddress }}' CONT_ID +``` + +** TODO ** Investigar mas el `format` + + +### docker build ### + +Como construir una imagen + +`docker build https://github.com/salvari/repo.git#rama:directorio` + +Tambien se le puede pasar una url o un directorio en nuestro pc. + + +## Dockerfile + +{{< admonition type=info title="Referencias" open=true >}} + +- [Dockerfile Best Practices with Examples](https://takacsmark.com/dockerfile-tutorial-by-example-dockerfile-best-practices-2018/#overview) + +{{< /admonition >}} + +Un _dockerfile_ es un fichero de texto que define una imagen de Docker. Con esto podemos crear nuestras imágenes a medida, ya sea para "dockerizar" una aplicación, para construir un entorno a medida, para implementar un servicio etc. etc. + +En el [dockerhub](https://hub.docker.com/) tenemos imágenes para hacer prácticamente cualquier cosa, pero en la práctica habrá muchas ocasiones donde queramos cambiar algún detalle o ajustar la imagen a nuestro gusto y necesidades (o simplemente aprender los detalles exactos de la imagen), para todo ello necesitamos saber como funcionan los _dockerfiles_. + +Un _dockerfile_ de ejemplo: + +```dockerfile +FROM python:3 + +WORKDIR /usr/src/app + +COPY requirements.txt ./ +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +CMD [ "python", "./your-daemon-or-script.py" ] +``` + +Un _dockerfile_ funciona por capas (_layers_) que son los bloques de construcción de _docker_. La primera _layer_ siempre es `FROM image_name` que define en que imagen pre-construida se basará nuestra imagen. Podemos definir muchas cosas en un _dockerfile_ desde permisos de usuario hasta _scripts_ de arranque. + +Asi que: + +1. Un _dockerfile_ es un fichero de texto que contien las instrucciones que se ejecutarán con `docker build` para construir una imagen Docker +2. Es un conjunto de instrucciones paso a paso +3. Se compone de un conjunto de instrucciones estándar (como `FROM`, `ADD`, etc. etc.) +4. Docker contruirá automáticamente una imagen a partir de esas instrucciones + +Nota: En _docker_ un contenedor es una imagen con una capa de lectura-escritura en la cima de muchas capas de solo-lectura. Esas capas inferiores se denominan "imágenes intermedias" y se generan cuando se ejecuta el _dockerfile_ durante la etapa de construcción de la imagen. + +Evidentemente para construir un _dockerfile_ no basta con conocer Docker. Si quiero construir una imagen para proveer un servicio de base de datos, necesito conocer como funciona ese servicio y el proceso de instalación del mismo para "dockerizarlo" + + +`ADD` +: Copia un fichero del host al contenedor + +`CMD` +: el argumento que pasas por defecto + +`ENTRYPOINT` +: el comando que se ejecuta por defecto al arrancar el contenedor + +`ENV` +: permite declarar una variable de entorno en el contenedor + +`EXPOSE` +: abre un puerto del contenedor + +`FROM` +: indica la imagen base que utilizarás para construir tu imagen + personalizada. Esta opción __es obligatoria__, y además __debe ser + la primera instrucción__ del Dockerfile. + +`MAINTAINER` +: es una valor opcional que te permite indicar quien es el que se encarga de mantener el Dockerfile + +`ONBUILD` +: te permite indicar un comando que se ejecutará cuando tu imagen sea utilizada para crear otra imagen. + +`RUN` +: ejecuta un comando y guarda el resultado como una nueva capa. + +`USER` +: define el usuario por defecto del contenedor + +`VOLUME` +: crea un volumen que es compartido por los diferentes contenedores o con el host +`WORKDIR define el directorio de trabajo para el contenedor. + +### Ejemplos de dockerfiles + +Primero + +```dockerfile +FROM python:3 + +WORKDIR /usr/src/app + +COPY requirements.txt ./ +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +CMD [ "python", "./your-daemon-or-script.py" ] +``` + +#### nginx + +``` +FROM alpine:3.15 AS builder +RUN apk add --update \ + --no-cache \ + pcre~=8.45 \ + libxml2~=2.9 \ + libxslt~=1.1 \ + gcc~=10.3 \ + make~=4.3 \ + libc-dev~=0.7 \ + pcre-dev~=8.45 \ + zlib-dev~=1.2 \ + libxml2-dev~=2.9 \ + libxslt-dev~=1.1 && \ + cd /tmp && \ + wget -q https://github.com/nginx/nginx/archive/master.zip -O nginx.zip && \ + unzip nginx.zip && \ + cd nginx-master && \ + ./auto/configure --prefix=/opt/nginx && \ + make && \ + make install && \ + apk del gcc make libc-dev pcre-dev zlib-dev libxml2-dev libxslt-dev && \ + rm -rf /var/cache/apk + +FROM alpine:3.15 + +ARG UID=${UID:-1000} +ARG GID=${GID:-1000} + +RUN apk add --update \ + --no-cache \ + pcre~=8.45 \ + libxml2~=2.9 \ + libxslt~=1.1 \ + tini~=0.19 \ + shadow~=4.8 &&\ + rm -rf /var/cache/apk && \ + groupmod -g $GID www-data && \ + adduser -u $UID -S www-data -G www-data && \ + mkdir /html + +COPY --from=builder /opt /opt + +COPY nginx.conf /opt/nginx/conf/nginx.conf +COPY entrypoint.sh / + +EXPOSE 8080 +VOLUME /html + +RUN chown -R www-data:www-data /html && \ + chown -R www-data:www-data /opt/nginx + +USER www-data + +ENTRYPOINT ["tini", "--"] +CMD ["/bin/sh", "/entrypoint.sh"] +``` + + +## Networks + +### Bridge + +```bash +docker network ls # Vemos las redes creadas + +docker run -dit --name alpine1 alpine ash +docker run -dit --name alpine2 alpine ash + +docker container ls # Vemos los dos alpine + +docker network inspect bridge # Vemos que se han conectado al bridge + +docker attach alpine1 # nos conectamos al contenedor + +ping alpine2 # NO FUNCIONA por nombre +ping 172.17.0.4 # FUNCIONA por ip + +# Para hacer un detach ordenado del alpine1 C-p C-q +``` + +Vamos a crear una red bridge propia (_user defined bridge_): + +```bash +docker network create --driver bridge alpine-net # Creamos la red +docker network ls # Vemos redes existentes +docker network inspect alpine-net + +docker run -dit --name alpine1 --network alpine-net alpine ash # Creamos tres alpines en la red bridge alpine-net +docker run -dit --name alpine2 --network alpine-net alpine ash +docker run -dit --name alpine3 --network alpine-net alpine ash +docker run -dit --name alpine4 alpine ash # este alpine4 está en la bridge default + +docker network connect bridge alpine3 # alpine3 está conectado a dos redes: default y alpine-net + +docker container ls +docker inspect alpine-net +docker inspect bridge + +docker attach alpine3 # nos conectamos a alpine3 +ping alpine1 # FUNCIONA, en redes definidas funciona + # por nombre y por IP + +ping alpine4 # NO FUNCIONA alpine3 y alpine4 se conectan + # por la default bridge que no resuelve nombres +# detach C-p C-q +``` + +Si probamos desde `alpine1` podremos hacer ping a los `alpine2` y +`alpine3`, por nombre o dirección ip, pero no podremos llegar a +`alpine4` de inguna manera, ya que está en otra red. + +---- +__IMPORTANTE__ +Un _docker-compose_ siempre crea una _user defined +bridge_, aunque no se lo indiquemos. + +---- + +## _Volumes_ y _Bind Mounts_ + +### Referencias + +* [Understanding Docker Volumes: Persisting a Grafana Configuration + File](https://www.datamachines.io/blog/understanding-docker-volume-persisting-a-grafana-configuration-file) +* [Stackoverflow: docker data volume vs mounted host directory](https://stackoverflow.com/questions/34357252/docker-data-volume-vs-mounted-host-directory) +* [Docker Storage](https://docs.docker.com/storage/) + +Ojito con esto por que, para mi, son muy diferentes y no encontré +ningún sitio que lo dejara claro desde el principio. Se crean con la +misma opción `-v` o `--volume` en el comando pero __hay diferencias__. + +### _Volumes_ + +En principio los contenedores son volátiles y eso está bien para +ciertas cosas se supone que tienes que diseñarlos para que sean muy +volátiles y que se puedan crear y matar fácilmente, pero en la +práctica no. Lo normal es que necesites tener datos persistentes, o +incluso datos compartidos entre varios contenedores. + + +* Un _volume_ es un espacio de almacenamiento que crea _Docker_ + internamente, le puedes dar nombre y _docker_ lo va a almacenar en + algún lugar del disco duro del _host_ (en linux tipicamente + `/var/lib/docker/volumes`) +* Se pueden crear previamente dándoles un nombre y separarlos de la + creación del contenedor: `docker volume create grafana-storage`. En + _docker-compose_ esto supone declararlos como _external_: + + ```docker-compose + volumes: + grafana-storage: + external: true + ``` + + Eso hace que el _docker-compose file_ no sea independiente (no + parece lo más elegante). + +* Si los declaramos en el fichero _docker-compose.yml_ se crearán en el + caso de que no existan (__parece la mejor manera__) + + ```docker-compose + volumes: + - web_data:/usr/share/nginx/html:ro + ``` + + O también: + + ```docker-compose + volumes: + web_data: + name: ${VOLUME_ID} + + services: + app: + image: nginx:alpine + ports: + - 80:80 + volumes: + - web_data:/usr/share/nginx/html:ro + ``` + +### _Bind Mounts_ + +En este caso montamos un fichero o directory __del sistema de ficheros +del host__ en el contenedor. + +### Diferencias entre _volumes_ y _bind mounts_ + +* Los _bind mounts_ son más simples a la hora de hacer copias de + seguridad (aunque en linux no veo grandes diferencias) +* Los _volumes_ se pueden gestionar desde el CLI del _Docker_ +* Los _volumes_ siempre funcionan, en cualquier S.O. así que tus + _dockerfiles_ o tus _docker compose files_ serán multiplataforma. +* Los _volumes_ se pueden compartir entre contenedores fácilmente. +* El almacenamiento de los _volumes_ es teoricamente más facil de + implementar en plataformas remotas (la nube) +* Los _bind mounts_ pueden dar problemas con los permisos de usuarios + (el usuario que se usa en el _container_ quizás no exista en el + _host_) +* Los _bind mounts_ son muy dependientes con el S.O. se suelen + declarar en variables de entorno para intentar desacoplar los + _dockerfiles_ y los _docker-compose files_ del S.O. que use cada + uno, por que las rutas de ficheros evidentemente dependen del S.O. + +### Renombrar un volumen (receta) + +```bash +#/bin/bash +docker volume create --name $2 +docker run --rm -it -v $1:/from -v $2:/to alpine ash -c "cd /from ; cp -av . /to" +[ $? -eq 0 ] && docker volume rm $1 +``` + + +## _docker-compose_ + +* [doc oficial](https://docs.docker.com/compose/compose-file) +* [edututorial](https://www.educative.io/blog/docker-compose-tutorial) + + +Es el siguiente paso en abstracción. Con _docker-compose_ podemos definir y ejecutar aplicaciones compuestas de múltiples contenedores. + +La definición se especifica mediante un fichero _YAML_ que permite configurar las aplicaciones y también crearlas. + +Las principales ventajas de _docker-compose_ son: + +* Múltiples entornos aislados en un unico _host_ +* Preservar los volúmenes de datos cuando se crean los contenedores +* Se recrean únicamente los contenedores que cambian +* Orquestar múltiples contenedores que trabajan juntos +* Permite definir variable y mover orquestaciones entre entornos + +### Flujo de trabajo + +1. Definir los entornos de aplicación con un _dockerfile_ +1. Definir los servicios provistos mediante _docker-compose_. Esto permitirá ejecutarlos en un entorno aislado. +3. Lanzar los servicios ejecutando el _docker-compose_ + +### _docker-compose_ fichero de configuración + +La estructura básica de un fichero de configuración para _docker-compose_ tiene esta pinta: + +```dockercompose +version: 'X' + +services: + web: + build: . + ports: + - "5000:5000" # host:container mejor siempre como string + volumes: + - .:/code + redis: + image: redis +``` + +Se puede ver más claro con un fichero real: + +```docker-compose +version: '3' +services: + web: + # Path to dockerfile. + # '.' represents the current directory in which + # docker-compose.yml is present. + build: . + + # Mapping of container port to host + ports: + - "5000:5000" + # Mount volume + volumes: + - "/usercode/:/code" + + # Link database container to app container + # for reachability. + links: + - "database:backenddb" + + database: + + # image to fetch from docker hub + image: mysql/mysql-server:5.7 + + # Environment variables for startup script + # container will use these variables + # to start the container with these define variables. + environment: + - "MYSQL_ROOT_PASSWORD=root" + - "MYSQL_USER=testuser" + - "MYSQL_PASSWORD=admin123" + - "MYSQL_DATABASE=backend" + # Mount init.sql file to automatically run + # and create tables for us. + # everything in docker-entrypoint-initdb.d folder + # is executed as soon as container is up nd running. + volumes: + - "/usercode/db/init.sql:/docker-entrypoint-initdb.d/init.sql" +``` + +`version: '3'` + +: Indica la versión del compose-file que estamos usando ([lista de + versiones y manual de referencia]( https://docs.docker.com/compose/compose-file/)) + +`services` + +: Esta sección define los diferentes containers que queremos crear, en + nuestro ejemplo solo habrá dos "web" y "database" + +`web` + +: Marca el comienzo de la definición del servicio "web", en este + ejemplo sería un servicio implementado con _Flask_ + +`build` + +: Indica el _path_ al _dockerfile_ que define el servicio, en este + caso es relativo y el "." significa que el _dockerfile_ está en el + mismo directorio que el fichero _yaml_ del _compose_ + +`ports` + +: Mapea puertos desde el container a puertos del _host_ + +`volumes` + +: mapea sistemas de ficheros del host en el contenedor (igual que la + opción `-v` en _docker_) + +`links` + +: Indica "enlaces" entre contenedores, para la _bridge network_ + debemos indicar que servicios pueden acceder a otros + +`image` + +: alternativamente al _dockerfile_ podemos especificar que nuestro + servicio se basa en una imagen pre-construida + +`enviroment` + +: Permite especificar una variable de entorno en el contenedor (igual + que la opción `-e` en _docker_) + +### Gestión de variables en _docker-compose_ + +Las variables pueden definirse en varios sitios, por orden de +precedencia serían: + +1. Compose file (en una sección _enviroment_) +2. Variables de entorno del sistema (en nuestro intérprete) +3. _Enviroment file_, es un fichero en el mismo directorio que el + fichero de _compose_ con el nombre `.env` +4. En el _dockerfile_ +5. La variable no está definida + +La opción _Enviroment file_ es un fichero que se llama `.env`, podemos +usar otro nombre cualquiera incluyendo la directiva `env_file` en el +fichero _compose_. Por ejemplo: + +```docker +env_file: + - ./secret-stuff.env +``` + +Se supone que __no__ vas a subir tus secretos a un repo en la nube. El +fichero `.env` probablemente no deba estar incluido en _git_ en un +entorno de producción. + +[Variables, documentación oficial](https://docs.docker.com/compose/environment-variables/) + +### docker-compose comandos útiles + +Reconstruir un contenedor +: Cuando cambiamos la definición de un servicio dentro del fichero + `docker-compose.yml`, podemos reconstruir ese servicio con el + comando: + ```bash + docker-compose up -d --no-deps --build + ``` + Tenemos una opción más moderna que sería el comando: + ```bash + docker-compose build --no-cache + ``` + + O bien: + ```bash + docker-compose up -d --no-cache --build + ``` + + +## Mosquitto + Influxdb + Graphana con _docker_ y _docker-compose_ + +[referencia](https://github.com/Nilhcem/home-monitoring-grafana) + +Creamos un directorio para nuestro proyecto que llamamos `homesensor` +(por ejemplo) + +```bash +mkdir homesensor +cd homesensor +``` + +### Mosquitto + +Creamos un directorio para nuestro container de _Mosquitto_ + +```bash +mkdir 01_mosquitto +cd 01_mosquitto +``` + +#### Configuración de _mosquitto_ + +Creamos un fichero de configuración de *Mosquitto* `mosquitto.conf` con el siguiente +contenido: + +```conf +persistence true +persistence_location /mosquitto/data/ +log_dest file /mosquitto/log/mosquitto.log +allow_anonymous false +password_file /mosquitto/config/users +``` + +Con este fichero le indicamos a _Mosquitto_: + +* Qué queremos que tenga persistencia de datos. Así salvará su estado + a disco cada 30 minutos (configurable), de lo contrario usaría + exclusivamente la memoria. +* le indicamos en que _path_ debe guardar los datos persistentes + (`/mosquitto/data/`) +* configuramos el fichero de log (`/mosquitto/log/mosquitto.log`) +* prohibimos usuarios anónimos +* establecemos el listado de usuarios con su password + +---- +__Nota__: Puedes echar un ojo al fichero de ejemplo de configuración +de mosquitto arrancando un contenedor sin ninguna opción de mapeado de +volúmenes. O consultando la documentación de _mosquitto_. Hay +muchísimas más opciones de las que proponemos. + +---- + +Tendremos que preparar también un fichero `users` de usuarios para +establecer los usuarios y sus password. Podemos crear un fichero de +usuarios y passwords en claro con el formato: + +``` +mqttuser:53cret0 +``` + +Y cifrarlo con el comando `mosquitto_passwd -U ` + +Si solo tenemos un usuario lo podemos crear con el comando +`mosquitto_passwd -c ` + +---- +__Nota__: + +Nos adelantamos a los acontecimientos, pero si no tienes _mosquitto_ +instalado en tu máquina puedes cifrar el fichero de usuarios con un +contenedor de _mosquitto_ transitorio, que se encargue del cifrado: + +```bash +docker run --rm \ +-v `pwd`/users:/mosquitto/config/users \ +eclipse-mosquitto \ +mosquitto_passwd -U /mosquitto/config/users +``` +---- + + +#### _mosquitto_ en _docker_ desde linea de comandos + +Una vez creados estos dos ficheros podemos crear un contenedor +_mosquitto_ con _ podemos hacerlo desde linea de comandos. + +```bash +docker run -it -p 1883:1883 \ +-v `pwd`/mosquitto.conf:/mosquitto/config/mosquitto.conf \ +-v `pwd`/users:/mosquitto/config/users eclipse-mosquitto +``` + +`run` + +: Ejecutará el contenedor, si la imagen especificada no existe la + bajará del _dockerhub_ + +`-it` + +: La opción `i` ejecuta el contenedor en modo interactivo, la opción + `t` abrira un terminal contra el contenedor. + +`-p 1883:1883` + +: mapea el puerto 1883 del contendor al puerto 1883 del host + +``-v `pwd`/mosquitto.conf:/mosquitto/config/mosquitto.conf`` + +: mapea el fichero `mosquitto.conf` del directorio actual (`pwd`) en + el volumen del contenedor: `/mosquitto/config/mosquitto.conf` + +``-v `pwd`/users:/mosquitto/config/users`` + +: Igual que el anterior pero con el fichero `users` + +`eclipse-mosquitto` + +: La imagen que vamos a usar para crear nuestro contenedor, si no + especificamos nada se usará `:latest`, es decir la imagen más + reciente. + +Podemos probar esta configuración con los clientes de mqtt (tenemos +que tener instalado el paquete `mosquitto-clients` en nuestra máquina) + +En un terminal lanzamos el _subscriber_: + +```bash +mosquitto_sub -h localhost -t test -u mqttuser -P 53cret0 +``` + +Y en otro terminal publicamos un mensaje en el _topic_ test: + +```bash +mosquitto_pub -h localhost -t test -u mqttuser -P 53cret0 -m "Hola mundo" +``` + +Comprobaremos que los mensajes se reciben en el primer terminal. + + +### InfluxDB + +Vamos a instalar _InfluxDB_ en _docker_. Igual que con _Mosquitto_ +vamos a mapear los ficheros de configuración y los ficheros donde +realmente guarda los datos la base de datos en nuestro sistema de +ficheros local (o el del host) + +Estamos en el directorio `homesensor` y creamos el directorio +`02_influxdb` + + ```bash +mkdir 02_influxdb +cd 02_influxdb +``` + +Nos bajamos la imagen oficial de _InfluxDB_ con `docker pull influxdb` + +Podemos ejecutar un contenedor para probar la imagen con + +```bash +docker run -d --name influxdbtest -p 8086:8086 influxdb +``` + +La opción `-d` (_detached_) es opcional, si no la pones verás el log de +influxdb, pero no libera el terminal. + +Ahora desde otro terminal (o desde el mismo si usaste `-d`) podemos +conectarnos al contenedor con uno de los dos comandos siguientes: + +```bash +docker exec -it influxdbtest /bin/bash # con un terminal +docker exec -it influxdbtest /usr/bin/influx # con el cliente de base de datos +``` + +Dentro del cliente de base de datos podríamos hacer lo que quisieramos, por ejemplo: + +```sql +show databases +create database mydb +use mydb +``` + +Como hemos mapeado el puerto 8086 del contenedor al host (nuestro pc) +también podemos conectarnos a la base de datos con un cliente en +nuestro pc, con scripts de python, etc. etc. + +#### Dos maneras de lanzar el contenedor + +Podemos examinar la historia de la imagen docker `influxdb` con +`docker history influxdb`(desde un terminal en nuestro pc). Si sale +muy apelotonada podemos indicarle que no recorte la salida con `docker +history --no-trunc influxdb` + +Veremos que se define `ENTRYPOINT ["/entrypoint.sh"]` + +Podemos investigar el contenido del fichero `entrypoint.sh` abriendo un +terminal contra el contenedor `influxdbtest` + +Después de investigar el contenido del fichero `influxdbtest.sh` y el +contenido del fichero `init-influxdb.sh` veremos que hay dos formas de +lanzar un contenedor a partir de la imagen `influxdb` (También nos +podíamos haber leído [esto](https://hub.docker.com/_/influxdb) para +llegar a lo mismo más rápido) + +La primera forma de lanzar el contenedor es la que hemos usado hasta +ahora: + +```bash +docker run -d --name influxdbtest -p 8086:8086 influxdb +``` + +La segunda forma (más interesante para ciertas cosas) sería invocando +el script `init-influxdb.sh`: + +```bash +docker run --name influxdbtest influxdb /init-influxdb.sh +``` + +Con esta segunda forma de lanzar el contenedor tenemos dos ventajas: + +* podemos usar variables de entorno y scripts de inicio, como nos + explican en [la pagina de + dockerhub](https://hub.docker.com/_/influxdb) que citamos antes. +* podemos tener un directorio `/docker-entrypoint-initdb.d` en la raiz + de nuestro contenedor y cualquier script con extensión `.sh` (shell) + o `.iql` (Influx QL) se ejecutará al arrancar el contenedor. + +__Pero__ la segunda forma de lanzar el contenedor sólo es práctica +para crear y configurar una base de datos, no está pensada para lanzar +un contenedor "de producción" + +Cuando acabemos de jugar, paramos el contenedor y lo borramos: + +```bash +docker stop influxdbtest +docker rm influxdbtest +``` + +#### Configuración de _InfluxDB_ + +Vamos a preparar las cosas para lanzar un contenedor transitorio de +_InfluxDB_ que nos configure la base de datos que queremos usar. + +Después lanzaremos el contenedor definitivo que usará la base de datos +configurada por el primero para dejar el servicio _InfluxDB_ +operativo. + +Lo primero es hacernos un fichero de configuración para _InfluxDB_. +Hay un truco para que el propio _InfluxDB_ nos escriba el fichero de +configuración por defecto: + +```bash +cd homesensor/02_influxdb + +docker run --rm influxdb influxd config |tee influxdb.conf +``` + +Este comando ejecuta un contenedor transitorio basado en la imagen +`influxdb`, es un contenedor transitorio por que con la opción `--rm` +le decimos a _docker_ que lo elimine al terminar la ejecución. Este +contenedor lo unico que hace es devolvernos el contenido del fichero +de configuración y desaparecer de la existencia. + +Ahora tendremos un fichero de configuración `influxdb.conf` en nuestro +directorio `02_influxdb`. Este fichero lo tenemos que mapear o copiar +al fichero `/etc/influxdb/influxdb.conf` del contenedor (lo vamos a mapear) + +Crearemos también un volumen en nuestro contenedor que asocie el +directorio `/var/lib/influxdb` del contenedor al directorio en el +sistema de ficheros local: `homesensor/data/influxdb` De esta forma +tendremos los ficheros que contienen la base de datos en el sistema de +ficheros del host. + +También vamos a crear un directorio +`homesensor/02_influxdb/init-scripts` que mapearemos al directorio +`/docker-entrypoint-initdb.d` del contenedor. En este directorio +crearemos un script en _Influx QL_ para crear usuarios y políticas de +retención para nuestra base de datos. + +En el directorio `homesensor/02_influxdb/init-scripts`, creamos el +fichero: + +`influxdb_init.iql` + +```sql +CREATE RETENTION POLICY one_week ON homesensor DURATION 168h REPLICATION 1 DEFAULT; +CREATE RETENTION POLICY one_year ON homesensor DURATION 52w REPLICATION 1; +CREATE USER telegraf WITH PASSWORD 'secretpass' WITH ALL PRIVILEGES; +CREATE USER nodered WITH PASSWORD 'secretpass'; +CREATE USER pybridge WITH PASSWORD 'secretpass'; +GRANT ALL ON homesensor TO nodered; +GRANT ALL ON homesensor TO pybridge; +``` + +--- +__NOTA___: +Creamos el usuario telegraf con todos los privilegios, eso lo +convierte en administrador de _InfluxDB_. De momento lo vamos a dejar +así para facilitar las pruebas del contenedor _Telegraf_. + +--- + +¡Vale! Ya tenemos todo listo. Ahora tenemos que hacer dos cosas. + +1. Ejecutar un contenedor transitorio que va a crear + * Mediante variables de entorno (opciones `-e`): + * Un usario administrador de _InfluxDB_ + * Una base de datos `homesensor` + * Un usuario genérico de la base de datos `homesensor` que se llama `hsuser`. + * Mediante el script en _InfluxQL_: + * Un par de políticas de retención para la base de datos `homesensor` + * Tres usuarios de bases de datos, a los que damos privilegios + sobre la base de datos `homesensor`: + * `telegraf` + * `noderedu` + * `pybridge` +2. Una vez configurada toda la base de datos: activar el acceso seguro + en la configuración y lanzar el contenedor `influxdb` que se va a + quedar trabajando. + +Para el primer paso __configurar la base de datos__ el comando es: + +```bash +cd homesensor # ASEGURATE de estar en el directorio homesensor +sudo rm -rf data/influxdb + +docker run --rm \ + -e INFLUXDB_HTTP_AUTH_ENABLED=true \ + -e INFLUXDB_ADMIN_USER=admin -e INFLUXDB_ADMIN_PASSWORD=s3cr3t0 \ + -e INFLUXDB_DB=homesensor \ + -e INFLUXDB_USER=hsuser -e INFLUXDB_USER_PASSWORD=pr1vad0 \ + -v $PWD/data/influxdb:/var/lib/influxdb \ + -v $PWD/02_influxdb/influxdb.conf:/etc/influxdb/influxdb.conf \ + -v $PWD/02_influxdb/init-scripts:/docker-entrypoint-initdb.d \ + influxdb /init-influxdb.sh +``` + +---- +__IMPORTANTE__ + +* Si no pones la opción `-e INFLUXDB_HTTP_AUTH_ENABLED=true` los + comandos de creación de usuarios fallan sin dar error. +* Cada vez que lances el contenedor de iniciación __ASEGURATE__ de + borrar previamente el directorio `homesensor/data/influxdb` o + fallará todo sin dar errores. +---- + +Para el segundo paso, es __necesario__ habilitar la seguridad en el +fichero de configuración antes de arrancar el contenedor. En el +fichero `influxdb.conf` asegurate de cambiar la linea (de _false_ a _true_): + +```ini +[http] +- auth-enabled = false ++ auth-enabled = true +``` + +Y por fin, para arrancar el contenedor `influxdb` que se quedará dando +servicio, el comando es: + +```bash +cd homesensor # asegurate de estar en el directorio homesensor +docker run -d --name influxdb -p 8086:8086 \ + -v $PWD/data/influxdb:/var/lib/influxdb \ + -v $PWD/02_influxdb/influxdb.conf:/etc/influxdb/influxdb.conf \ + influxdb +``` + +Con el contenedor _influxdb_ disponible podemos conectarnos a la base +de datos, ya sea desde nuestra máquina (si tenemos el cliente +instalado en local) o abriendo un terminal al contenedor. + +Si ejecutamos: + +```bash +> show databases +ERR: unable to parse authentication credentials +Warning: It is possible this error is due to not setting a database. +Please set a database with the command "use ". +``` + +Nos da un error de autenticación. + +Si añadimos usuario y password entramos sin problemas y podemos vere +que la base de datos `homesensor` y todos los usuarios se han creado +correctamente. + +```bash +influx --username admin --password s3cr3t0 +Connected to http://localhost:8086 version 1.8.3 +InfluxDB shell version: 1.8.3 +> show databases +name: databases +name +---- +homesensor +_internal +> show users +user admin +---- ----- +admin true +idbuser false +telegrafuser false +nodereduser false +pybridgeuser false + +``` +Un ejemplo de continous query para el dia que haga falta: + +```sql +CREATE CONTINUOUS QUERY "cq_30m" ON "homesensor" +BEGIN + SELECT mean("temperature") AS "mean_temperature",mean("humidity") AS "mean_humidity" \ + INTO "a_year"."acum_ambient" + FROM "ambient" + GROUP BY time(30m) +END; + +``` + +### Telegraf + +Preparamos un fichero de configuración de _Telegraf_. _Telegraf_ es de +la familia _InfluxDB_ así que soporta el mismo truco, para volcar una +configuración por defecto: + +```bash +cd homesensor +mkdir 03_telegraf +cd 03_telegraf +docker run --rm telegraf telegraf config |tee telegraf.conf +``` + +En _Telegraf_ todo se hace en el fichero de configuración, en el se +programan las entradas y las salidas. + +#### Usando `container:network` + +Este escenario lo pongo por que me pareció un caso interesante. Pero +no veo que sea posible implementarlo en _docker-compose_ así que no +será el definitivo. + +1. Lanzamos el container `influxdb` + +```bash +docker run -d --name influxdb -p 8086:8086 \ + -v $PWD/data/influxdb:/var/lib/influxdb \ + -v $PWD/02_influxdb/influxdb.conf:/etc/influxdb/influxdb.conf \ + -v $PWD/02_influxdb/init-scripts:/docker-entrypoint-initdb.d \ + influxdb +``` +2. Modificamos el fichero de configuración de _Telegraf_ para poner usuario y password. + +```init +## HTTP Basic Auth +username = "telegraf" +password = "secretpass" +``` + +3. Lanzamos el contenedor `telegraf` + +```bash +docker run -d --name telegraf \ + --network=container:influxdb \ + -v /proc:/host/proc:ro \ + -v $PWD/03_telegraf/telegraf.conf:/etc/telegraf/telegraf.conf:ro \ + telegraf +``` + +Si nos conectamos con el cliente `influx` al servidor de bases de +datos (o nos conectamos al contenedor y ejecutamos el cliente) veremos +que _Telegraf_ está mandando todas las estadísticas que tiene +definidas por defecto de nuestro host. + +Para que todo esto funcione: + +* Hemos mapeado el directorio `/proc` del linux de nuestro host + (nuestro pc o servidor) en el directorio `/host/proc` del contenedor + `telegraf`, así que _Telegraf_ puede leer varias estadísticas de + nuestro _host_ (es decir nuestro pc) y escribir los distintos + valores como _measurements_ en la base de datos `telegraf` (es el + nombre de la base de datos que usa por defecto). +* El contenedor `telegraf` ha creado esta base de datos que no existía + gracias a que el usario que le hemos configurado para acceder a la + base de datos tiene permisos de administración, de lo contrario + tendríamos que haber creado nosotros la base de datos antes de que + se conectara. +* Hemos usado la opción `--network=container:influxdb`, esta opción + hace que el contenedor `telegraf` se cree en el `network stack` del + contenedor `influxdb`, en la práctica el _Telegraf_ se cree que está + en la misma máquina que _InfluxDB_ y se comunica via el interfaz + _loopback_ Esta opción tan curiosa creo que no se puede implementar + en _docker-compose_ (al menos yo no he encontrado nada parecido) + + +### Grafana + +Grafana se puede configurar a través de su propia interfaz web. + + +---- __NOTA__: + +No me aclaro con [la documentación de _Grafana_ para +_Docker_](https://grafana.com/docs/grafana/latest/installation/docker/), +la única forma en que he conseguido mapear la base de datos en el +directorio local `data/grafana` ha sido lanzar el contenedor como +_root_. No se que riesgos implica eso. + +---- + +Lanzamos el contenedor: + +```bash +docker run --user root -d --name=grafana -p 3000:3000 \ + -v $PWD/data/grafana:/var/lib/grafana \ + grafana/grafana +``` + + +La primera vez que nos conectamos a grafana tenemos que entrar con el +usuario `admin` con contraseña `admin`. Nos pedirá cambiar la +contraseña. + + +* Nos conectamos a +* Configuramos la contraseña del administrador +* Añadimos InfluxDB como fuente de datos. En este escenario tenemos que + configurar la dirección IP del contenedor `influxdb` así que + tendremos que usar algún comando del estilo `docker inspect + influxdb` o `docker inspect bridge |grep influxdb -A 5` para + averiguar la dirección IP. + * __name__ influxdb + * __query language__ InfluxQL + * __http url__ + * __Access__ server + * __Auth__ Basic auth with credentials + * __user__ telegraf (con su contraseña) + * __database__ telegraf, configurar usuario y contraseña + * __http method__ GET +* Por ultimo el report de grafana lo importamos de uno que hay + pre-construido para este escenario con el id 1443 + + [Grafana Data Sources tutorial](https://grafana.com/tutorials/grafana-fundamentals/?utm_source=grafana_gettingstarted#1) + + + +### Solución completa con _docker-compose_ + +#### _mosquitto_ en _docker-compose_ + +Definiremos el servicio _mosquitto_ en nuestro fichero +`homesensor/docker-compose.yml` + + +```docker +version: '3' + +services: + mosquitto: + image: eclipse-mosquitto + container_name: mosquitto + ports: + - 1883:1883 + volumes: + - ./01_mosquitto/mosquitto.conf:/mosquitto/config/mosquitto.conf + - ./01_mosquitto/users:/mosquitto/config/users + - ${DATA_DIR}/mosquitto/data:/mosquitto/data + - ${DATA_DIR}/mosquitto/log:/mosquitto/log + restart: always +``` + +Como se puede ver en el fichero `homesensor/docker-compose.yml` hemos +definido dos volúmenes adicionales para tener accesibles desde el +_host_ el directorio de datos: `/mosquitto/data` y el directorio de +logs `/mosquitto/log` + +El mapeado de los nuevos volúmenes está controlado por una variable +`DATA_DIR` que podemos pasar desde el entorno de sistema o con un +fichero `.env` para que apunte, por ejemplo, al directorio +`homesensor/data` en el host. + +`.env` + +```bash +DATA_DIR=./data +``` + + +Ahora podemos lanzar el contenedor con `docker-compose up` (asegúrate +de borrar el contenedor que creamos a mano, y de establecer la +variable `DATA_DIR` de alguna manera) + +Una vez lanzado el contenedor podemos hacer pruebas con los clientes +mqtt instalados y comprobaremos que los datos y el log de _mosquitto_ +se guardan en el host. + +Otra cosa que podemos comprobar al usar _docker-compose_ es que se +crea una red específica y segregada para nuestros contenedores. + +```bash +docker network ls +NETWORK ID NAME DRIVER SCOPE +d48756f76467 bridge bridge local +315e4570dff1 homesensor_default bridge local +9b579976258b host host local +51c50bab6f35 none null local + +docker inspect homesensor_default + +. +. +. + "Containers": { + "39a45dde629a0ab6f834e7c59f0d9785c64ba31b0d3d93c6c8d2622d31607c28": { + "Name": "cmosquitto", + "EndpointID": "504ae74b9109e6d87cd2359f7d6b17352388a5a9a48f7bba5aa7ad29fa43977e", + "MacAddress": "02:42:ac:1b:00:02", + "IPv4Address": "172.27.0.2/16", + "IPv6Address": "" + } + }, +. +. +. +``` + +Podemos ver que ahora tenemos la red `homesensor_default` y que dentro +de esa red solo tenemos el contenedor `mosquito`. + + + +#### _InfluxDB_ en _docker-compose_ + +Añadimos la configuración de _InfluxDB_ a nuestro fichero `dockercompose.yml` + +#### Grafana en _docker-compose_ + +Añadimos la configuración del servicio + + + + + + + + +## Recetas + +### Backup de volúmenes + +Los pasos típicos + +1. Parar todos los contenedores que usen los volúmenes que queremos salvar +2. Arrancar un contenedor dedicado para hacer los backups. Tiene que montar los volúmenes que queremos salvaguardar y montar también un volumen (probablemente un _bind-mount_, donde salvaremos los backups) +3. Hacer los backups +4. Parar el contenedor de backups +5. Arrancar los contenedores de producción + +Un ejemplo: + +```bash +docker stop targetContainer +mkdir ./backup +docker run --rm --volumes-from targetContainer -v ~/backup:/backup ubuntu bash -c “cd /var/lib/targetCont/content && tar cvf /backup/ghost-site.tar .” + +``` + +### Ejecutar aplicaciones X11 en un contenedor Docker + +Supongamos que queremos ejecutar una aplicación X11 en un contenedor y verla en el escritorio del Host. + +Tenemos dos alternativas, lanzar la aplicación contra el Xdisplay de nuestro ordenador (`echo "$DISPLAY"`) o preparar otro _display_ dedicado a nuestro contenedor. + +{{< admonition type=tip title="Otras vias" open=false >}} + +[Aquí](https://www.howtogeek.com/devops/how-to-run-gui-applications-in-a-docker-container/) describen como lanzar aplicaciones via el _X11 socket_ o utilizando VNC. Pero yo creo que las recetas que apunto son más fáciles. + +{{< /admonition >}} + + +#### Usar el Xdisplay de nuestro escritorio + +Las ventajas de esta opción es que la aplicación del contenedor aparecerá simplemente como una ventana más en nuestro escritorio, no deberíamos tener ningún problema a la hora de usar nuestros dispositivos de entrada (ratón, teclado, etc.) y de salida (pantalla básicamente). + +1. Averiguar nuestro display con `echo "$DISPLAY"` +2. Autorizar las conexiones a nuestro display, por seguridad están prohibidas + + ```bash + xhost + # Esto equivale a autorizar todas las conexiones + # Es mejor NO USAR esta opción + + xhost +"local:docker@" # Esta opción es mucho más segura + # solo permite conexiones desde nuestros contenedores + ``` + +3. Lanzar nuestro contenedor estableciendo la variable de entorno `$DISPLAY` y fijando el `net` de tipo `host`: + + ```bash + docker run --rm --it --net=host -e DISPLAY=$DISPLAY + ``` + +El detalle importante aquí es limitar lo más posible las conexiones autorizadas a nuestro Xdisplay + + +#### Lanzar otro display dedicado + +Yo suelo usar _Xephyr_ para hacer estos experimentos. _Xephyr_ es un servidor de display anidado. _Xephyr_ nos da un Xdisplay al que podemos conectar aplicaciones X11 pero que funciona como una ventana dentro de nuestro entorno gráfico (es decir está anidado en el Xdisplay de nuestro escritorio). + +La ventaja de esta opción es que no abrimos autorizaciones al Xdisplay de nuestro sistema. A cambio tendremos que ver como usa Xephyr nuestro teclado y ratón. + +1. Instalar _Xephyr_ + ```bash + sudo apt install xserver-xephyr + ``` + +2. Lanzar una ventana de _Xephyr_ + + ```bash + echo "$DISPLAY" # Averiguamos nuestro DISPLAY, que suele ser el 0 o el 1 + # HAY QUE USAR UNO DISTINTO PARA Xephyr + + Xephyr -ac -screen 800x600 -br -reset -terminate 2> /dev/null :1 & # Arrancamos el Xserver + ``` + + Opciones de Xephyr utilizadas: + - __-ac__ + + Autorizar conexiones de clientes indiscriminadamente (disable access restrictions) + - __-screen__ + + Especificar la geometría de la pantalla + - __-br__ + + La ventana raiz tendrá fondo negro + + - __-reset__ + + Reset al terminar el último cliente + + - __-terminate__ + + Finalizar cuando se resetee el servidor + - __2> /dev/null__ + + Mandar los mensajes de error al limbo (alias NE en nuestro pc) + - __:1__ + + Arrancar el server en el DISPLAY=1 __TIENE QUE SER DISTINTO DEL DISPLAY DEL SISTEMA__ + +3. Por último lanzamos nuestro contenedor igual que en la primera opción, pero con el Xdisplay correspondiente a _Xephyr_ + + ```bash + docker run --rm --it --net=host -e DISPLAY=":1" + ``` + + +## Referencias + +* [Getting Started (oficial)](https://docs.docker.com/get-started/) +* [El tutorial de atareao](https://www.atareao.es/tutorial/docker/) +* [The smart person guide to Docker](https://www.techrepublic.com/article/docker-the-smart-persons-guide/) +* [Lista de versiones de compose-file y manual de referencia]( https://docs.docker.com/compose/compose-file/) +* [Kitematic: un interfaz gráfico para docker](https://kitematic.com/) +* [Podman, un docker alternativo](https://www.atareao.es/podcast/es-podman-la-alternativa-a-docker/) + +* [Docker-compose tutorial](https://www.educative.io/blog/docker-compose-tutorial) <-- No está mal +* [Advanced Docker-compose configuration](https://runnable.com/docker/advanced-docker-compose-configuration) +* [Docker Compose Tutorial](https://vegibit.com/docker-compose-tutorial/) + +* [Awesome Compose](https://github.com/docker/awesome-compose) + + +### influxdb +* https://thenewstack.io/how-to-setup-influxdb-telegraf-and-grafana-on-docker-part-1/ +* https://lazyadmin.nl/it/installing-grafana-influxdb-and-telegraf-using-docker/ +* https://dev.to/project42/install-grafana-influxdb-telegraf-using-docker-compose-56e9 + + + +### uwsgi and traefik + +* https://blog.miguelgrinberg.com/post/running-your-flask-application-over-https +* https://beenje.github.io/blog/posts/running-your-application-over-https-with-traefik/ +* https://dockerquestions.com/2020/03/23/route-to-flask-and-vue-containers-with-traefik/ +* https://www.fullstackpython.com/wsgi-servers.html +* https://gist.github.com/nknapp/20c7cd89f1f128b8425dd89cbad0b802 +* https://medium.com/@tiangolo/full-stack-modern-web-applications-using-python-flask-docker-swagger-and-more-b6609dedb747 +* https://stackoverflow.com/questions/44639958/nginx-behind-traefik-docker-swarm-mode-real-ip + + +### nginx and traefik +* [Static files with nginx and traefik](https://www.simplecto.com/use-traefik-with-nginx-apache-caddyserver-serve-static-files/) +* [Install traefik](https://www.howtoforge.com/tutorial/ubuntu-docker-traefik-proxy/) +* [Traefik and worpress, as usual](https://www.digitalocean.com/community/tutorials/how-to-use-traefik-as-a-reverse-proxy-for-docker-containers-on-ubuntu-16-04) +* [nginx behind traefik question](https://stackoverflow.com/questions/44639958/nginx-behind-traefik-docker-swarm-mode-real-ip) + + + +* https://docs.ovh.com/gb/en/domains/create_a_dns_zone_for_a_domain_which_is_not_registered_at_ovh/ + + + +### Kibana and Elastic Search + +* [elastic kibana and docker-compose](https://codingfundas.com/how-to-install-elasticsearch-7-with-kibana-using-docker-compose/index.html) +* [quick start](https://www.devopsroles.com/quick-start-install-elasticsearch-and-kibana-with-docker/) +* [docker logs elastic and kibana](https://www.sarulabs.com/post/5/2019-08-12/sending-docker-logs-to-elasticsearch-and-kibana-with-filebeat.html) diff --git a/content/posts/notes_general/notes_domotica.md b/content/posts/notes_general/notes_domotica.md new file mode 100644 index 0000000..ec999a5 --- /dev/null +++ b/content/posts/notes_general/notes_domotica.md @@ -0,0 +1,684 @@ +--- +weight: 4 +title: "Apuntes de Domótica" +date: 2020-11-03T18:01:27+0100 +draft: false +summary: "Notas sobre Domótica" +categories: +- notes +tags: +- domotica +- micros +- mqtt +- mosquitto +- influxdb +- grafana +- homeassistant +--- + +Apuntes **no terminados** sobre Domótica Libre + + + +{{< admonition type=warning title="Work in progress" open=true >}} +Estos apuntes no están completos, (ni de lejos) +{{< /admonition >}} + + +## Grupo de investigación + +* Fundado el 2020-11-02, tenemos grupo de telegram y BBB. +* [Cryptpad del grupo de domótica](https://cryptpad.fr/pad/#/2/pad/edit/+K6cC9t-UGKfem6AdmkJ93ja/) + + +## Hardware para empezar + +* __Wemos__: [este](https://es.aliexpress.com/item/32843602421.html) + es el micro procesador que vamos a usar para el sensor. Se puede + programar con el IDE Arduino. Este es el ESP8285, equivale a un + ESP8266 con 1Mb de flash. +* __Sensor de temperatura/humedad__ +* __RTC y tarjeta SD__ Como [este](https://es.aliexpress.com/item/32826265932.html) +* __Pantalla OLED__ Como + [esta](https://es.aliexpress.com/item/1005001636650130.html) + compatible con la mecánica Wemos +* __Módulo de gestión de batería__ Como + [este](https://es.aliexpress.com/item/32796003002.html).Para + alimentar el sensor con una batería de 3,3 volt + +Ver la tabla de componentes +[aquí](https://cryptpad.fr/sheet/#/2/sheet/edit/PYqEabbLSfwUlQticILskoOC/) + +## MQTT + +_**M**essage **Q**ueuing **T**elemetry **T**ransport_, es un protocolo +**ligero** (bajo consumo de memoria y cpu) diseñado especificamente +para que dispositivos de bajo consumo transmitan datos de banda +estrecha (con velocidades muy bajas). El protocolo funciona sobre +TCP/IP (otro protocolo, "el de internet"). Es un protocolo viejecito +(1999), lo que pasa es que se ha puesto de moda por que encaja muy +bien con todo el tema de IoT (_**I**nternet **o**f **T**hings_) + +### Conceptos + +_Broker_ + +: Es un _middleware_ (un intermediario) entre los _publishers_ + (agentes que envían datos) y _receivers_ (agentes que reciben los + datos) + +_Publisher_ + +: Un agente que envía datos al _Broker_, puede ser un dispositivo + sensor, un demonio monitorizando un servidor, una raspi, etc. etc. + +_Subscriber_ + +: Es el receptor de los datos enviados por el _Publisher_ + +_Topic_ + +: Los _Publisher_ y _Subscriber_ no se relacionan directamente. Cuando + un _publisher_ envía datos al _broker_ los asocia a un _topic_. El + _topic_ es una cadena en forma de jerarquia (p.ej. + /sensor/cocina/temperatura), el carácter '/' actúa como separador de + nivel, cada campo separado por '/' representa un nivel. Los puedes + organizar como quieras. Cuando un _subscriber_ se conecta al + _broker_ especifica los _topic_ en los que está interesado. El + _broker_ le hará llegar todos los mensajes de esos _topic_. + + Tenemos dos operadores o máscaras para especificar las + subscripciones: `#` y `+`. + + Supongamos que tenemos los siguientes _topic_: + + * `/sensor/cocina/temperatura` + * `/sensor/cocina/humedad` + * `/sensor/cocina/humo` + * `/sensor/salon/temperatura` + * `/sensor/salon/humedad` + * `/sensor/salon/humo` + + Con la especificación `/sensor/+/temperatura` me suscribo a todos + los sensores de temperatura. `+` encaja con un nivel simple. + + Con la especificación `/sensor/#` me suscribo a todos los sensores. + `#` encaja con múltiples niveles. + + Si organizamos los niveles de los _topic_ con lógica, podremos + gestionar fácilmente las suscripciones. + +_message_ + +: Los datos que son enviados y recibidos + +### Versiones del protocolo + +* V3.1.1 +* V5.0 + +### Brokers MQTT + +Hay muchos brokers, esta lista es muy parcial pero todos los que están +tienen licencia libre. + +* __Mosquitto__: Mosquitto es un broker sencillo y que aparece en un + montón de tutoriales disponibles en internet. Está incluido en los + repos de Debian y Ubuntu (seguramente no muy actualizado) y hay ppa + disponible. La principal pega de Mosquitto es que no soporta + "clustering" (varios brokers funcionando en paralelo) que es la + forma natural de escalar la carga en el servido y dar redundancia. + Es práctico para desarrollar aplicaciones pero puede dar problemas + si lo quieres usar en un entorno de producción. +* __RabittMQ__: Es un broker multiprotocolo así que no solo soporta + MQTT. El soporte para distintos protocolos de colas de mensajes se + implementa mediante plugins. Soporta clustering así que es escalable + para aguantar cargas grandes de peticiones y se puede implementar + redundancia. El soporte de MQTT no es completo, no tiene soporte + para MQTT V5.0 ni para todas las carácteristicas de MQTT V3.1.1 (por + ejemplo no soporta QoS2) +* __VerneMQ__: Es un broker MQTT puro (no es multiprotocolo) Soporta + clustering y todas las versiones de MQTT, aparentemente con todas + sus funcionalidades. + +### Pruebas con _Mosquitto_ + +Vamos a hacer pruebas con el _broker_ más sencillo: _Mosquitto_ + +En Ubuntu y derivadas es muy fácil de instalar, añadimos el origen de +paquetes oficiales para estar a la última, instalamos el paquete +`mosquitto` y el paquete `mosquitto-clients`, los clientes nos vendrán +bien para hacer pruebas. En otras distribuciones tendrás que mirar +como se instala, pero no puede ser muy complicado. + +```bash +sudo apt-add-repository ppa:mosquitto-dev/mosquitto-ppa +sudo apt-get update +sudo apt install mosquitto mosquitto-clients +``` + +En Ubuntu y derivadas **queda lanzado el servicio** y podemos hacer +pruebas con los clientes instalados. Si después queremos instalar el +_Mosquitto_ de otra manera (con _docker_ por ejemplo) hay que +acordarse de parar el servicio. + +#### Probando + +En un terminal con el comando `mosquitto_sub -h localhost -t test` +lanzamos un _subscriber_ que atiende al _topic_ test. + +Desde otro terminal con el comando `mosquitto_pub -h localhost -t test +-m "Hola Mundo"` ejecutamos un _publisher_ que manda el mensaje "Hola +Mundo" al _topic_ test. + +En la primera ventana podemos ver: + +```bash +$ mosquitto_sub -h localhost -t test +Hola mundo +``` + +Se ha recibido el mensaje en el _topic_ suscrito. + +#### Probando con python + +Vamos a ver como usar el _mqtt_ desde Python. Así queda mucho más +claro como funciona el _broker_ + + +Creamos un entorno virtual y lo activamos (yo lo he llamado `paho`), +en ese entorno instalamos la librería: `pip install paho-mqtt` + +Creamos un programa _subscriber_: + +```python +import paho.mqtt.client as mqtt +import time + + +# la rutina a ejecutar cuando recibimos un mensaje +def on_message(client, userdata, message): + print("received message: ", str(message.payload.decode("utf-8"))) + + +# la rutina a ejecutar al recibir CONNACK +def on_connect(client, b, c, rt): + print("Connected with result " + str(rt)) + + # hacemos la suscripcion dentro de la rutina on_connect + # de esta forma si tenemos reconexiones ya nos aseguramos + # la suscripcion + client.subscribe("ALEATORIOS") + + +# dirección del broker +mqttBroker = "localhost" + +# creamos un "cliente" +client = mqtt.Client("subs-1") +client.on_connect = on_connect +client.on_message = on_message + +# conexión al broker +client.connect(mqttBroker) + +# lanzamos un loop no bloqueante +client.loop_start() + +# Escuchamos durante 30 segundos y paramos el programa. +time.sleep(30) +client.loop_stop() +``` + + +Y este sería el programa _publisher_ + +```python +import paho.mqtt.client as mqtt +from random import randrange, uniform +import time + + +# definimos la rutina a ejecutar cuando recibimos CONNACK +def on_connect(client, userdata, flags, rc): + print("Connected with result " + str(rc)) + + +# definimos la dirección del broker +mqttBroker = "localhost" + +# creamos un "cliente" +client = mqtt.Client("publisher-1") +client.on_connect = on_connect + +# conexión al broker +client.connect(mqttBroker) + +# Bucle infinito, publicamos un número aleatorio y esperamos 1 sg +while True: + randNumber = uniform(0, 10) + client.publish("ALEATORIOS", randNumber) + print("Publicado " + str(randNumber) + " en el topic ALEATORIOS") + time.sleep(1) +``` + +Podemos ver los programas en acción ejecutando cada uno en su terminal +(terminal=ventana de comandos) ¡Recuerda activar el entorno virtual +en todos los terminales que abras! + +##### ¿Qué está pasando aquí? + +Cada vez que desde un cliente le pedimos algo al _broker_ este nos +responde con una señal, pero ¡ojo! el protocolo es asíncrono, así que +mas que una conversación entre cliente y servidor tenemos un +intercambio de correos. + +Por ejemplo, cuando nosotros le mandamos la petición de conexión al +_broker_ debería llegarnos un _CONNACK_ desde el mismo. En el +_CONNACK_ el _broker_ nos adjunta tres campos: _user data_, _flags_ y +_result code_. El campo más interesante probablemente sea el _result +code_, estos son los valores definidos en el protocolo: + +| rc | Significado | +| :-----: |-------------------------------------------------| +| 0 | Connection successful | +| 1 | Connection refused – incorrect protocol version | +| 2 | Connection refused – invalid client identifier | +| 3 | Connection refused – server unavailable | +| 4 | Connection refused – bad username or password | +| 5 | Connection refused – not authorised | +| 6-255 | Currently unused. | + +La biblioteca _paho-mqtt_ nos permite definir rutinas ___callback__ +que la librería invocará cuando se produzcan ciertos eventos. + +Un par de ejemplos: un evento de conexión reconocida (se ha recibido +el _CONNACK_) hará que se ejecute el _callback on_connect_, el evento +"recibir un mensaje" hará que _paho_ intente ejecutar el _callback +on_message_. Esta tabla cubre los principales eventos: + +| Evento | _Callback_ | +|---------------------------|------------------| +| Conexión reconocida | `on_connect` | +| Desconexión reconocida | `on_disconnect` | +| Suscripción reconocida | `on_subscribe` | +| De-suscripción reconocida | `on_unsubscribe` | +| Publicación reconocida | `on_publish` | +| Mensaje recibido | `on_message` | +| Info de log disponible | `on_log` | + +Los ejemplos que puse son muy sencillos, si queremos escribir un +código más robusto hay que tener en cuenta todo lo que el protocolo +ofrece y que es un protocolo asíncrono. Un par de ejemplos de mejoras + +* La función `on_connect` debería comprobar el _result code_ y no + dejar continuar el programa sin tener una conexión verificada. +* La función `on_message` seguramente tendrá que procesar el _topic_ + del mensaje y puede que también la _qos_ + + +## influxdb + +* +* +* +* + + +_Influxdb_ es una base de datos especializada en series temporales de +datos. Eso hace que sea mucho más eficiente que una base de datos +relacional tratando este tipo de datos. Aunque internamente cambia +bastante el funcionamiento el lenguaje de consultas no es muy +diferente de SQL. Otra ventaja relacionada con nuestro escenario es que +se integra muy bien con _Grafana_. + +Hasta ahora _influxdb_ viene con varias aplicaciones asociadas: + +_Telegraf_ + +: un agente de recolección de datos, para capturar datos de _stacks_ y + otros sistemas + +_Cronograf_ + +: Un visualizador de datos al estilo de _Graphana_ + +Kapacitor + +: un motor de procesado de datos en tiempo real especializado en + series temporales + +Pero está disponible la versión __InfluxDB OSS 2.0__ que integra todas +las herramientas en un único programa. Por lo que veo la instalación +es por binarios únicamente y además __no es compatible con +Raspberry__. Parece interesante pero por ahora lo dejamos. + +Si instalamos el _Influxdb_ clásico nos quedamos con la versión 1.8.3 +(en este momento). Una vez instalada y arrancado el servicio lo +podemos probar preguntando por las bases de datos disponibles (solo +está la _internal_) + +```bash +$ influx +Connected to http://localhost:8086 version 1.8.3 +InfluxDB shell version: 1.8.3 +> show databases +name: databases +name +---- +_internal +> +``` + +Que no os despiste el interfaz http, no es _human friendly_ ni +funciona con navegadores (en esta versión de _influxdb_). Si que +podemos interrogarlo con _curl_ por ejemplo y nos responderá en +perfecto _json_: + +```bash +$ curl -G http://localhost:8086/query --data-urlencode "q=SHOW DATABASES" + +{"results":[{"statement_id":0,"series":[{"name":"databases","columns":["name"],"values":[["_internal"]]}]}]} +``` + +Por lo demás trabajar con _InfluxDB_ no es tan diferente de trabajar +con una base de datos relacional de toda la vida. + +* _InfluxDB_ está optimizada para trabajar con series de datos + temporales, es más rápida (con este tipo de datos) que las bases de + datos relacionales clásicas +* Cambia un poco la terminología: + * La base de datos clásica era una colección de tablas, la de + _Influx_ puede verse como una colección de "medidas" (_measurements_) + * Las "medidas" se parecen a las tablas de las bases de datos + relacionales, pero: + * Siempre tienen que tener una referencia temporal + (_timestamp_, basicamente nanosegundos desde el _Linux Epoch_) + * Se distingue entre campos (_fields_) y etiquetas (_tags_), + equivalen a los campos (columnas) de las tablas de las bases + de datos relacionales, pero: **las etiquetas están + indexadas** y los campos no. + * los registros, las filas de las tablas, se llaman puntos +* Se pueden ajustar las políticas de retención de datos, es decir, + cuanto tiempo se mantienen en la base de datos. Depende de para que + quieras usar los datos, a lo mejor quieres un histórico de una hora + o de cinco años. Ajustar bien las políticas de retención influye en + el rendimiento de la base de datos. + +### Instalación de _InfluxDB_ + +Dependerá del sistema operativo, por ejemplo para Linux Mint: + +```bash +wget -qO- https://repos.influxdata.com/influxdb.key | sudo apt-key add - +#source /etc/lsb-release +DISTRIB_ID=ubuntu +DISTRIB_CODENAME=focal +echo "deb https://repos.influxdata.com/${DISTRIB_ID} ${DISTRIB_CODENAME} stable" | sudo tee /etc/apt/sources.list.d/influxdb.list + +apt update +apt install influxdb +apt install telegraf +``` + +Una vez instalado verifica que el servicio está arrancado. + +El cliente de la base de datos se llama _influx_ si ejecutamos el +cliente podemos preguntar por las bases de datos presentes con: `show +databases` (solo muestra la base de datos interna) + +Una sesión completa +* Creamos la base de datos +* La ponemos como base de datos activa +* Insertamos datos + +```bash +influx -precision rfc3339 +Connected to http://localhost:8086 version 1.8.3 +InfluxDB shell version: 1.8.3 +> show databases +name: databases +name +---- +_internal + +>create database mydb +> show databases +name: databases +name +---- +_internal +mydb + +> use mydb +Using database mydb + +> insert temperature,location=campo external=13,internal=23 +> insert temperature,location=ciudad external=19, internal=22 +``` + +Las lineas de los _insert_ siguen el esquema: + +`[,=...] =[,=...] [unix-nano-timestamp]` + +El primer grupo son los _tags_ que **están indexados**. En nuestro +ejemplo el _tag_ sería `location`. El segundo grupo son los _fields_ +(no indexados), en nuestro ejemplo tendríamos dos `external` e +`internal`. Opcionalmente podemos escribir el _timestamp_ o dejar que +_influx_ lo ponga automáticamente (pondrá el instante en que se +realiza la inserción) + +### Un ejemplo con python + +Lo primero es instalar la biblioteca de python así que en nuestro +entorno virtual ejecutamos `pip install influxdb` + +Un programa python chorras para ejecutar las inserciones del ejemplo anterior: + +```python +from influxdb import InfluxDBClient + +client = InfluxDBClient(host=localhost, port=8086) +client.switch_database('mydb') +client.write_points("temperature,location='countryside' external=13,internal=23", protocol='line') +client.write_points("temperature,location='city' external=19,internal=22", protocol='line') +``` + +Si después de ejecutar el programa vamos al cliente _influx_: + +```bash +> use mydb +Using database mydb +> show measurements +name +---- +temperature +> select * from temperature +name: temperature +time external internal location +---- -------- -------- -------- +2020-11-27T15:23:20.523830387Z 13 23 countryside +2020-11-27T15:23:33.459690565Z 19 22 city +``` + + + + +## Análisis y Visualización de datos + +* __Metabase__: Visualización de gráficos sencilla y potente. +* __Grafana__: Plataforma de visualización y análisis de datos muy completa. + + + + +## node-red + + +### Instalación en Linux + +Para instalar node-red en linux necesitamos instalar primero +`node.js`. Hay varias formas de instalar `node.js`, yo voy a optar por +instalar `nvm` que es el **n**ode **v**ersion **m**anager. + +Para ello ejecutamos el siguiente comando (la versión actual de `nvm` +es la 0.37.0) + +```bash +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.0/install.sh | bash +``` + +El script de instalación añade las siguientes lineas al fichero +`~/.bashrc`, nosotros las eliminamos de `~/.bashrc` y las movemos al +fichero `~/.profile` + +```bash +export NVM_DIR="$HOME/.nvm" +[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm +[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion +``` + +Para comprobar la instalación usamos `command -v nvm` que nos +devolverá `nvm`. `which` no funciona en este caso por que es un script +pensado para invocarlo con `source` + +### Instalación de `node.js` + +Ahora que tenemos `nvm` instalado, ya podemos instalar fácilmente la versión o versiones que queramos de `node.js` + +```bash +nvm ls-remote # para listar las versiones disponibles +nvm install node # instala la última versión disponible +nvm install --lts # instala la versión LTS +``` + +### Instalación de `node-red` + +Una vez instalado el `node.js` instalar el `node-red` es muy fácil: +activar el `node.js` que hemos instalado y hacer la instalación de +`node-red` + +```bash +nvm use node +npm install -g --unsafe-perm node-red +``` + + +## Instalación del IDE Arduino en Linux para trabajar con Wemos D1 + +Descargamos el IDE de la [pagina +web](https://www.arduino.cc/en/software). Para proceder a la +instalación se puede consultar [la guía de postinstalación de Linux +Mint](https://gitlab.com/salvari/linuxmint_ulyana_20) que detalla +todos los pasos. + +Solo necesitamos habilitar el soporte para ESP8266, añadiendo la linea +siguiente a nuestras preferencias de Arduino: + +`http://arduino.esp8266.com/stable/package_esp8266com_index.json` + + + +## Configuración de mini-PC como servidor + +Describimos la instalación en un mini-pc Beelink T34 (con 8 gb de RAM +y 256Gb de disco duro SSD). No es obligatorio hacerlo como +describimos, esto es solo para referencia. + +Nos hemos decidido por instalar Debian en el mini-pc. Hay varias +formas de instalar Debian, pero como nosotros queremos un Debian sin +interfaz gráfico y con lo mínimo, creemos que lo más fácil es usar el +`netinst`. + +Descargamos la imagen iso de `netinst` desde [la página de +Debian](https://cdimage.debian.org/debian-cd/current/amd64/bt-cd/) (la +hemos descargado via torrent) + +Desde linux hemos usado Etcher (es multiplataforma) para grabar la +image iso descargada en una memoria USB. + +Conectamos nuestro mini-PC a un teclado usb y a un monitor (hemos +usado un coversor HDMI-VGA) y conectamos la memoria USB en algúno de +los puertos libres, también le hemos dado internet con un cable +ethernet a nuestro router. + +Al arrancar el mini-PC veremos un aviso: pulsando F7 nos deja elegir +el dispositivo de arranque. Elegimos arrancar desde la memoria USB e +iniciamos la instalación de Debian, con particionado manual y sin LVM. + +Hemos creado tres particiones: + +| Particion | Tamaño | Tipo | Punto de montaje | Comentarios | +|-----------|--------|------|--------------------|-----------------------------------------------------------| +| /dev/sda1 | 511Mb | EFI | /boot/efi | La crea el programa de instalación | +| /dev/sda2 | 55Gb | ext4 | / | root | +| /dev/sda4 | 171Gb | ext4 | /store | Dejamos casi todo el espacio disponible en esta partición | + + +El programa de instalación nos ha pedido drivers adicionales pero lo +hemos ignorado. + +Hemos escogido idioma inglés con teclado español. También hemos +escogido locales `es_ES-UTF-8` + +**Solo** hemos instalado el openssh server y las herramientas básicas. +Tendremos que instalar muchas más cosas pero eso nos deja un sistema +lo más limpio posible. + +Tras completar la instalación el sistema **no** arranca normalmente se +queda pillado en el _prompt_ de _grub_. Tras teclear `exit` en el +prompt arranca normalmente. + +Se soluciona ejecutando como _root_ `grub-install` y ya no vuelve a fallar. + +Una vez arrancado el sistema podemos conectarnos via ssh. + + + + + + +## Referencias + +### Wiki de Bricolabs + +* [Raspberry Pi, configuración para IOT](https://bricolabs.cc/wiki/guias/raspberry_iot) +* [Sistema IOT de Bricolabs](https://bricolabs.cc/wiki/proyectos/sistema_iot_bricolabs) + +### Referencias MQTT + +* [Buenos apuntes para aprender de MQTT](https://www.hivemq.com/mqtt-essentials/) +* [Para empezar con python y MQTT (muy completo)](http://www.steves-internet-guide.com/into-mqtt-python-client/) +* [Enviar datos desde ESP via mqtt](https://diyi0t.com/microcontroller-to-raspberry-pi-wifi-mqtt-communication/) +* [Visualizar datos (cont. del ant.)](https://diyi0t.com/visualize-mqtt-data-with-influxdb-and-grafana/) + +### Referencias _Influxdb_ + +* [Getting started](https://docs.influxdata.com/influxdb/v1.7/introduction/getting-started/) +* [Getting started with Influxdb OSS 2.0](https://docs.influxdata.com/influxdb/v2.0/get-started/) +* [Guia de Influxdb](https://devconnected.com/the-definitive-guide-to-influxdb-in-2019/) +* [Instrucciones de instalación para la versión clásica y la nueva 2.0 en ubuntu y debian](https://devconnected.com/how-to-install-influxdb-on-ubuntu-debian-in-2019/#Option_2_Adding_the_repositories_to_your_package_manager) +* [otro getting started](https://www.influxdata.com/blog/getting-started-with-influxdb-2-0-scraping-metrics-running-telegraf-querying-data-and-writing-data/) +* [Python e Influxdb OSS 2.0](https://www.influxdata.com/blog/getting-started-with-python-and-influxdb-v2-0/) +* [Docker compose: Grafana/Influx/Telegraf](https://dev.to/project42/install-grafana-influxdb-telegraf-using-docker-compose-56e9) +* [idem](https://lazyadmin.nl/it/installing-grafana-influxdb-and-telegraf-using-docker/) +* [idem, tiene dos partes](https://thenewstack.io/how-to-setup-influxdb-telegraf-and-grafana-on-docker-part-1/) +* [Acumulados y caducidad: _Continous query and retention policy_](https://docs.influxdata.com/influxdb/v1.8/guides/downsample_and_retain/) + +### Home assistant seguros + +* +* +* https://makezine.com/projects/mycroft-the-open-source-private-voice-assistant-on-raspberry-pi/> +* Magpi vol 99, pag 54 + +### Ejemplos del escenario básico que queremos implementar + +* [Visualizar datos con mqtt-influxdb-grafana (bridge-python)](https://diyi0t.com/visualize-mqtt-data-with-influxdb-and-grafana/) +* [Una configuración con Telegraf](https://screenzone.eu/arduino-mqtt-sensor-grafana/) +* [nilhcem (web)](http://nilhcem.com/iot/home-monitoring-with-mqtt-influxdb-grafana) + +### Para revisar + +* [Pagina especializada en IoT](https://diyi0t.com/) +* diff --git a/content/posts/notes_general/notes_editors.md b/content/posts/notes_general/notes_editors.md new file mode 100644 index 0000000..a2dc44f --- /dev/null +++ b/content/posts/notes_general/notes_editors.md @@ -0,0 +1,58 @@ +--- +weight: 4 +title: "Editores" +date: 2022-06-18T12:44:28+0200 +draft: true +summary: "Apuntes de distintos editores para programar en Linux" +categories: +- notes +tags: +- emacs +- neovim +- pycharm +- codium +- vs_code +--- + + +## Editores + +### Emacs + +#### lsp-mode + +**TODO** Revisar comando lsp-workspace-add-folder et al + +### Neovim + +### Codium + +En ___Codium___ y ___VS Code___ las herramientas soportadas para los entornos virtuales de Python no incluyen `pyenv`. + +Podemos definir un _fichero de definiciones de variables de entorno_ identificado por el `python.envFile`, por defecto tiene valor `${workspaceFolder}/.env` **TODO COMPLETAR ESTO** + +Podemos establecer los _workspace settings_ con `C-,` y `Extensions: Python` y la opción `Edit in settings.json`. El ficher `json` del _workspace_ quedará: + +```json +{ + "folders": [ + { + "path": "." + } + ], + "settings": { + "python.pythonPath": "/home/salvari/.pyenv/versions/ve_celery/bin/python", + "python.linting.enabled": true, + "python.linting.pylintEnabled": true, + "python.linting.pylintPath": "/home/salvari/.pyenv/versions/ve_celery/pylint", + + "python.linting.pylintArgs": [ + "--init-hook", + "import sys; sys.path.append(\"/home/salvari/.pyenv/versions/ve_celery/lib/python3.9/site-packages\");", + ], + "python.autoComplete.extraPaths": [ + "/home/salvari/.pyenv/versions/ve_celery/lib/python3.9/site-packages", + ] + } +} +``` diff --git a/content/posts/notes_general/notes_fediverso.md b/content/posts/notes_general/notes_fediverso.md new file mode 100644 index 0000000..b5bf398 --- /dev/null +++ b/content/posts/notes_general/notes_fediverso.md @@ -0,0 +1,220 @@ +--- +weight: 4 +title: "Apuntes del Fediverso" +date: 2022-05-01T18:16:57+0200 +draft: false +summary: "Apuntes del Fediverso, la red social federada" +categories: +- notes +tags: +- fediverso +- mastodon +--- + +- [Decentralized Social Media](https://www.fosslife.org/mastodon-basics) from Linux Magazine +- [Traefik v2 and Mastodon, a wonderful couple!](https://www.innoq.com/en/blog/traefik-v2-and-mastodon/) (Innoq version) +- [Github for the article above](https://gist.github.com/smashnet/38cf7c30cb06427bab78ae5ab0fd2ae3) +- [Traefik v2 and Mastodon, a wonderful couple!](http://nicolas.inden.one/article/2020/01/24/traefik-v2-and-mastodon.html) (blog version) +- [An all-in-one Mastodon Image](https://github.com/Wonderfall/docker-mastodon) +- [Hubzilla and Traefik](https://framagit.org/anaqreon/hubzilla-docker) +- [Making your own Mastodon Service in ten steps](https://growyourown.services/making-your-own-mastodon-server-in-10-steps/) +- [Image Descriptions on the Fediverse](https://m0yng.uk/Image-descriptions-on-the-Fediverse/) +- [A Hitchhiker's Guide to the Fediverse](https://gofoss.net/fediverse/) +- [Fossllife: Mastodón Basics](https://www.fosslife.org/mastodon-basics) +- [Mastodon Docker Setup with Traeffic via php scripts](https://gitlab.com/dweipert.de/devops/mastodon-docker-traefik) +- [Feditips](https://mstdn.social/@feditips/108215973331591896) (mastodon account) +- [Migadu](https://www.migadu.com/index.html) Email service for Domains +- [Fediverse Party](https://fediverse.party/) +- [Comunidades Fediversales en Español](https://notas.niboe.info/s/6lr2U2myb#) +- [Fedi-Directory](https://fedi.directory/) la colección de @FediFollows + +## ¿Qué es el Fediverso? + +El origen del Fediverso puede situarse en proyectos como ___Diaspora___ o ___Identi.ca___, los primeros intentos de crear un red social descentralizada. Con el tiempo evolucionaron, el creador de de _Identi.ca_ Evan Prodromou desarrolló _pump.io_, un _framework_ que permitía a los desarrolladores construir servicios de redes sociales. + +Con el tiempo el [W3C]^(World Wide Web Consortium) vio las ventajas de _pump.io_ y tomándolo como base publicó la especificación y recomendación ___ActivityPub___. + +Y sobre _ActivityPub_ nació el __Fediverso__. + +_ActivityPub_ es un protocolo y una API que permite a los servicios definidos con ella la comunicación a través del Fediverso con otros nodos tanto del mismo servicio como de otros servicios diferentes. Por ejemplo, puedes publicar una foto en tu cuenta de ___Pixelfed___ (un servicio para compartir fotos) y hacer que esa foto aparezca en las lineas temporales de la gente que te siga en Mastodon, el servicio de microblogging que es quizás el buque insignia del Fediverso. + +El Fediverso no solo comparte publicaciones, los nodos son capaces de compartir recursos entre ellos e incluso a través de técnicas P2P entre clientes. Por ejemplo si publicas un video en una instancia de ___PeerTube___ (puede que en tu propio servidor) Necesitarás bastante ancho de banda pero menos del que crees, los clientes que estén viendo el mismo video compartirán la carga. + +Además de ___Mastodon___, ___Peertube___ y ___Pixelfed___, podríamos citar una plataforma para compartir audio dirigida a músicos y podcasters llamada ___Funkwhale___. También podríamos hablar de ___Hubzilla___ que se autodefine como "una potente plataforma para crear sitios web interconectados que ofrecen identidad descentralizada, comunicaciones y una infraestructura de permisos, todo ello basado en tecnología común de servidores web.", de ___Akhoma___, ___Misckey__ o ___Calckey___ para microblogging, de ___Bookwyrm___ para hablar de libros, etc. etc. etc. + +En resumidas cuentas, el __Fediverso__ es una red Federada, compuesta de muchas redes sociales interconectadas. El número de usuarios total según la estimación más reciente ronda los cuatro millones de personas. + +__Descentralizado__ + +: En vez de un único proveedor de servicios hay multitud de proveedores que ofrecen acceso al __Fediverso__. Cualquiera puede configurar un servidor (normalmente denominados instancias) ya sea para uso personal o para dar servicio a una comunidad + +__Federada__ + +: Los servidores de distintos servicios se comunican entre si mediante el protocolo ___ActitityPub___ + +__No comercial__ + +: Aquí los usuarios no son el producto, el __Fediverso__ no está diseñado para cautivar la atención del usuario o mantenerlo enganchado a la red + +__Neutral__ + +: No hay filtros para priorizar o recomendarte contenido. Los contenidos se muestran en orden cronológico. Los filtros los establece el usuario + +### Cosas en el Fediverso + +Mastodon +: Microblogs +: [Listado de instancias](https://joinmastodon.org/) Hay muchas instancias de Mastodon para escoger + +Pixelfed +: Compartir tus fotos. +: - +: - [Instancia principal](https://pixelfed.social/) + +PeerTube +: Red federada para compartir videos +: - +: - +: [Sepia Search](https://search.joinpeertube.org/) + +BookWrym +: Red social federada para compartir tus lecturas, hablar de libros, escribir reseñas y descubrir que es lo siguiente que vas a leer. +: - +: - + +Funkwhale +: Una red social para compartir y escuchar música +: [Get started](https://funkwhale.audio/#get-started) +: [Instance List](https://network.funkwhale.audio/dashboards/d/overview/funkwhale-network-overview?orgId=1&refresh=2h) + +Friendica +: Es una plataforma de comunicaciones descentralizada que puede conectarse con plataformas independientes como el Fediverso o Diaspora o comerciales como Twitter. + +Lemmy +: Una plataforma libre y descentralizada de compartición y discusión de enlaces. + +Pleroma +: Otra plataforma de microblogging + +Misskey +: Otra plaforma más de microblogging + +Goldfish +: Parecido a TikTok + +Gotosocial +: Microblogging facílisimo de autoalojar en docker. + +## ¿Qué es Mastodon? + +__Mastodon__ es un servicio de microblogging que ahora mismo parece ser el servicio más conocido del __Fediverso__. + +__Mastodon__ puede parecerse mucho a Twiter, pero la verdad es que son muy diferentes no te confundas. + +Para empezar __en Mastodon no hay un algoritmo__ para recomendar contenidos (y mantenerte pegado a la pantalla de tu dispositivo). Normalmente si acabas de llegar a la red te parecerá todo muy silencioso y no hay mecanismos automáticos para que deje de serlo. + +Aquí eres tú quien tiene que buscar y seguir a gente interesante. Es importante cuidar tu linea de tiempo __local__ (el conjunto de gente que sigues) para tener una experiencia de calidad (hay muchas herramientas para el ajuste fino). + +Para empezar puedes buscar algún _hastag_ interesante y descubrirás seguramente gente para seguir. Una vez que sigas a alguien puedes cotillear a quien sigue, para descubrir a mas gente. No hay que tener miedo de seguir gente, y más importante, no dudes en dejar de seguirlos si no te gusta lo que publican. Hay soluciones "más ligeras", puedes silenciar temporalmente una cuenta que se enrolla hablando de algo que no te interesa. Y también hay soluciones más drásticas, puedes silenciar o bloquear a cuentas que te resulten ofensivas e incluso silenciar o bloquear servidores enteros. + +Como resultado de esta forma de funcionar, el número de gente que te siga es bastante irrelevante, de hecho el interfaz puede configurarse para ocultartelo a ti mismo. Lo mismo ocurre con los favoritos que obtenga una publicación (sólo los ve el autor). Y ninguna de esas métricas influye en el alcance de tus publicaciones. La información se difunde exclusivamente por _reblogs_ de gente que te lea y de la gente que lea a tus seguidores. + +Si tienes mucho interes en acrecentar tu audiencia usa los _hashtags_ prolífica y cuidadosamente. + +Sigue gente y _hastag_ que te gusten. Además puedes activar la alerta en la gente que sea importante para ti. + +Organiza a la gente que te sigue en listas para facilitar la lectura. + +Siempre puedes seguir via RSS a la gente (y hastags) que quieras. + +Escoge un interfaz de usuario lo más avanzado posible, la experiencia será muy diferente con el interfaz adecuado. + +__Sigue siempre al administrador de tu instancia__, salvo que sea muy brasas considera la posibilidad de activar la campana. Hay que estar atento tanto a sus avisos como a sus peticiones de ayuda. + +### Detalles + +* Cuando quieres difundir el _toot_ (un _post_) de alguna cuenta que sigues lo único que puedes hacer es un _boost_ (_reblog_), los favoritos y los marcadores (_bookmarks_) son cosas privadas en Mastodon (y en el Fediverso en general) +* Los favoritos se usan para mostrar reconocimiento, aprecio o acuerdo con un _toot_ (escoge como quieres interpretarlos), quedan almacenados en tu perfil para que puedas visitarlos en el futuro. +* Un _bookmark_ es estrictamente privado, el autor del _toot_ no sabrá que te lo has apuntado. +* El _boost_ se usa para difundir lo que sea, animar el diálogo o compartir cosas que te gustan. +* Los _hastag_ son importantes, se usan en búsquedas y además la gente puede seguir _hastags_ +* Si publicas en varios idiomas asegúrate de configurar correctamente el idioma de cada publicación. Es muy importante para que la gente pueda filtrar y ver solo las publicaciones que pueda leer. + +### Recetas Mastodónticas + +Mastodon provee varias herramientas para la gestión de tu linea temporal. + +* Puedes añadir notas a cualquier perfil. P.ej. para recordar por qué empezaste a seguirlo, si es un perfil ofensivo, cualquier cosa que quieras recordar +* Puedes deshabilitar los _boost_ usuario a usuario. Podrías querer ver los post de un usuario pero no todo lo que le interese. +* Puedes silenciar (_"mutear"_) temporalmente una cuenta +* Puedes filtrar post por palabras específicas, _hashtags_ o frases +* Y como última medida puedes Bloquear, dejar de seguir o silenciar definitivamente. +* Todas las cuentas de Mastodon tienen RSS, puedes suscribirte por ejemplo a mastodon.technology/comacero.rss +* Modula el alcance de tus _post_, mira si quieres que sean públicos, sólo para seguidores, sólo para mencionados, no-listados, etc. etc. + +#### Verificación de la cuenta + +Mastodon tiene un mecanismo de verificación. Necesitas tener un sitio web en el que seas conocido. + +Enlaza tu perfil en Mastodon desde el sitio web. Puede ser un enlace visible o una etiqueta de enlace en la cabecera del html con un atributo `rel=me`. + +Enlaza la página web desde tu perfil de Mastodon. Deberías ver que aparece una marca verde de verificación en el perfil. + +#### Usa _hashtags_ + +Las busquedas en Mastodon funcionan sobre _hashtags_, además con las versiones más modernas de Mastodon puedes seguir _hashtags_. Es importante poner los _hashtags_ al final de tu _post_ y usar notación _CamelCase_. Las dos recomendaciones son para que las aplicaciones de accesibilidad (texto a voz por ejemplo) funcionen correctamente. + +#### Usa _alt-text_ en la imágenes + +Mastodon nació como una red inclusiva, usa _alt-text_ en todas las imágenes para hacerlas accesibles a todo el mundo. + +#### Usa listas + +Si tienes intereses muy variados o perteneces a varias comunidades distintas las listas te permiten mantener a toda la gente que sigues ordenada y facilitan mucho la lectura. Puedes asignar a cualquier persona que sigas a una o varias listas. + +#### Threads o listas encadenadas de posts + +Asegúrate de poner en "publico" solo el post cabecera y el resto en "unlisted" para mantener la linea temporal limpia. + +#### Usa filtros + +Aun lo estoy estudiando, pero son muy útiles. + + + +#### Misc + +Quizá estás aterrizando en #Mastodon o quizás, ya llevas un ratito por aquí, pero un detalle importante a tener en cuenta es: + +‼️No compartir en otros sitios web o redes sociales enlaces a contenidos publicados originalmente en Mastodon sin el consentimiento de sus autores.‼️ + +‼️En #Mastodon las búsquedas por palabras clave sólo devuelve resultados de contenidos publicados por el propio usuario, incluyendo sus etiquetas, artículos que ha marcado como favoritos, los que ha reenviado o aquellos en los que ha sido mencionado.‼️ + +‼️La función de citar inevitablemente agrega toxicidad a los comportamientos de las personas. Estás tentado a citar cuando deberías estar respondiendo, por lo que hablas a tu audiencia en lugar de a la persona con la que estás hablando. Se vuelve performativo. Incluso cuando lo haces por "bien", como ridiculizar comentarios horribles, estás dando más atención a los comentarios horribles...‼️ + + + +## Calckey + +Su instancia tiene 5 diferentes líneas de tiempo habilitadas + + La línea de tiempo Inicio es donde puedes ver las publicaciones de tus seguidores. + La línea de tiempo Local es donde puedes ver las publicaciones de todos los demás en esta instancia. + La línea de tiempo recomendada es donde puedes ver las publicaciones de las instancias que los administradores recomiendan. + La línea de tiempo Social es donde puedes ver las publicaciones de los amigos de tus seguidores. + La línea de tiempo Global es donde puedes ver las publicaciones de todas las demás instancias conectadas. + +### Configuración + +- Activar la opción de _Show local post in **Social Timeline**_ + + +### Varios + +- Cuando Calckey dice que no puede cargar un medio se pueden cargar desde el servidor remoto. diff --git a/content/posts/notes_general/notes_foto.md b/content/posts/notes_general/notes_foto.md new file mode 100644 index 0000000..c16099a --- /dev/null +++ b/content/posts/notes_general/notes_foto.md @@ -0,0 +1,82 @@ +--- +weight: 4 +title: "Apuntes de Fotografía" +date: 2022-04-24T23:36:44+0200 +draft: true +summary: "Mis. apuntes de fotografía" +categories: +- notes +tags: +- fotografía +- darktable +- olympus +--- + +## Olympus + +{{< admonition type=note title="Work In Progress" open=true >}} + +PENDIENTE DE COMPLETAR + + +{{< /admonition >}} + + +## Cianotipia + +### Preparación de los liquidos + +Materiales necesarios: + +- Ferrocianuro de potasio (también llamado prusiato rojo) +- Citrato férrico amoniacal (también llamado citrato verde) +- Agua destilada (sospecho que el cloro del agua puede interferir en el preparado) +- Guantes de proteccion +- Gafas de protección (por si acaso) +- Jeringuilla para medir mililitros (mejor si tienes dos) +- Tres botes opacos (no demasiado grandes) +- Cinta de carrocero para etiquetar y/o hacer aun más opacos los botes. +- Brochas o esponjas para extender los líquidos sobre el papel o la tela +- Una hoja de Goma Eva del mismo tamaño que el negativo (p.ej. A4) +- Un cristal del mismo tamaño que el negativo (p.ej. A4 o un cristal de un marco de fotos de tamaño adecuado) +- Material base para la cianotipia, puede ser tela o papel que aguante bien la inmersión en agua (papel de acuarela) +- Negativos de acetato + +#### Composición de los líquidos + +Vamos a adoptar el convenio de que la solución de Citrato Férrico Amoniacal o Citrato Verde será la ___Solución A___, evidentemente la de Ferrocianuro de Potasio o Prusiato Rojo será la ___Solución B___ + +- Receta 1 + - 8 gr. de prusiato rojo en 100 ml de agua + - 20 gr. de citrato verde en 100 ml de agua + +- Receta 2 + - 10 - 12 gr. de ferrocianuro de potasio (prusiato rojo) + - 25 gr. de citrato férrico amoniacal (citrato verde) + +Al parecer conviene hacer la preparación de los liquidos con poca luz. + +Evidentemente es __muy importante__ mantener los líquidos aislados, no está de más usar diferentes jeringuillas para preparar cada uno o de lo contrario limpiar exhaustivamente después de hacer un liquido y antes de preparar el segundo. + +Los líquidos deben conservarse a oscuras y bien cerrados pueden llegar a durar un año. + + + + +## Darktable + +### Detalles del darkroom + +* ___Darktable___ siempre muestra todas y cada una de las operaciones que realiza sobre la imagen +* Todos los módulos de ___Darktable___ tienen un orden de aplicación predefinido (se supone óptimo) El orden en el que en realidad se aplican los módulos a la imagen puede verse en la pestaña de módulos activos del _darkroom_. +* Hay distintas colecciones predeterminadas de módulos que podemos activar para no tener la lista completa de módulos (que es enorme). También podemos crear nuestra propia colección. +* El panel de acceso rápido (_Quick access_) __contiene versiones resumidas__ de los módulos más habituales, pero siempre podemos ir al módulo completo con el atajo de la esquina superior derecha. +* Debajo de la imagen a la izquierda tenemos acceso a los presets (sólo los creados por nosotros) y a los estilos. +* Todos los módulos se pueden resetear a los valores por defecto +* Todos los "deslizantes" pueden accionarse con el botón derecho y teclear un valor concreto si queremos + +### Flujo de trabajo + +* Importar las fotos (generalmente desde las cámaras) con ___Shotwell___. +* Añadir los directorios de ___Shotwell___ a la biblioteca de ___Darktable___ +* Hacer una revisión en la mesa de luz de todas las fotos y destruir las malas diff --git a/content/posts/notes_general/notes_freecad.md b/content/posts/notes_general/notes_freecad.md new file mode 100644 index 0000000..2afd112 --- /dev/null +++ b/content/posts/notes_general/notes_freecad.md @@ -0,0 +1,135 @@ +--- +weight: 4 +title: "Notas de FreeCAD" +date: 2021-06-28T09:35:03+0200 +draft: true +summary: "Notas sobre FreeCAD" +categories: + - notes +tags: + - freecad +--- + +Apuntes **muy incompletos** sobre _FreeCAD_. + + + +{{< image src="/images/FreeCAD-Logo.png" >}} + +{{< admonition type=warning title="Work in progress" open=true >}} +Estos apuntes son personales y no valen para aprender, mejor búscate un buen tutorial en la red +{{< /admonition >}} + + +## FreeCAD + +### Ejecutar varias versiones de FreeCAD con sus configuraciones independientes + +Yo uso las Appimages de FreeCAD, para cada configuración independiente necesitamos un directorio diferente. En mi caso uso tres directorios: + +* `~/apps/freecad/fc019` Para FreeCAD 0.19 +* `~/apps/freecad/fc020` Para FreeCAD 0.20 +* `~/apps/freecad/fcrt` Para FreeCAD RealThunder + +Cada vez que descargo una nueva versión de Appimage, la descargo al directorio correspondiente. + +En cada directorio tengo un enlace simbólico `current` apuntando a la Appimage que quiero ejecutar (el enlace simbólico se crea con el comando `ln `) . Por ejemplo, en el directorio `fc019` ahora mismo tengo: + +```bash +ls -l ~/apps/freecad/fc019 +total 1647152 +lrwxrwxrwx 1 salvari salvari 56 Jun 28 09:12 current -> FreeCAD_0.19-24291-Linux-Conda_glibc2.12-x86_64.AppImage +-rwxr--r-- 1 salvari salvari 839349440 Apr 26 21:55 FreeCAD_0.19-24276-Linux-Conda_glibc2.12-x86_64.AppImage +-rwxr--r-- 1 salvari salvari 847320256 Jun 28 08:55 FreeCAD_0.19-24291-Linux-Conda_glibc2.12-x86_64.AppImage +``` + +Por último tengo tres scripts en `~/.local/bin` que se llaman `fc019`, `fc020` y `fcrt`. Pongo el contenido de `fc019` como ejemplo: + +```bash +#!/usr/bin/env bash + +export HOME=/home/salvari/apps/freecad/fc019 +export FREECAD_USER_HOME=$HOME +~/current --user-cfg ~/user.cfg --system-cfg ~/system.cfg --module-path ~/Mod +``` + +Con estos scripts puedo lanzar los tres FreeCAD y las configuraciónes de cada uno quedarán guardadas en su respectivo directorio. + +### Letras con FreeCAD e Inkscape + +En Inkscape: + +* Escribimos el texto que nos interese. Hay que fijarse en darle ya las dimensiones que queremos en realidad +* Selecionamos el objeto texto y ejecutamos el comando `Path::Object to Path` (también vale `Ctrl+alt+C`) +* Salvamos el texto en un fichero `texto.svg` + +En FreeCAD: +* Abrimos un nuevo fichero e importamos el SVG como geometría. + +* Para cada letra del texto tendremos uno o varios `path`: uno con el contorno de la letra y uno por cada "hueco". Nos aseguramos de estar en el *Draft Workbench* y para cada `path` hacemos un `Modification::Downgrade` y un `Modification::Upgrade`, con eso transformamos cada `path` en un `wire` + +* Ahora pasamos al *Part Workbench* y a cada `wire` le aplicamos un `Part::2D offset`. +* Volvemos al *Draft Workbench* y al objeto `2D offset` le hacemos un downgrade. Esto descompone el objeto en el `wire` original y un conjunto de `edges` (que quedan seleccionados). Si a continuación hacemos un upgrade (con todos los `edge` seleccionados) tendremos un `wire` para la silueta externa de la letra y otro `wire` para la interna (conviene renombrarlos) +* Para que todo vaya bien (no tengo otra explicación) hay que pasar los `wire` a objetos `Shape2DView`, y a cada uno de los objetos resultantes aplicarles la operación `Draft to Sketch`. Con eso obtendremos dos objetos `sketch` para los contornos exterior e interior de la letra respectivamente. +* Es muy conveniente pasar al *Sketcher Workbench* y validar ambos objetos `sketch` buscando y arreglando los `open vertexes`. +* Ya podemos ir al *Part Design Workbench*, antes de nada conviene crear un objeto `Part` en la raiz de nuestro documento y un objeto `body` dentro del `Part` +* Arrastramos el `sketch` exterior al `Body` y hacemos un `pad` a nuestro gusto +* Arrastramos el `sketch` interior al `Body` pero teniendo cuidado de mapearlo a la cara superior de nuestra letra. +* Con el `sketch` mapeado en la cara superior creamos un `Pocket` controlando la profundidad podemos hacer que nuestra letra sea una caja o solo una pared + +## Tutoriales + +### Rafael García + +[Aquí](https://www.youtube.com/watch?v=136sG-7zins&list=PLvDOxR8gzkX_3LtuEKBIoNLFFgJK_AXkE&index=1) + +* FreeCAD 001: Intro +* FreeCAD 002: Más intro con el sketcher +* FreeCAD 003: Más intro con el sketcher (un poco de splines) +* FreeCAD 004: Boceto sobre caras y algo de matrices polares +* FreeCAD 005: Revolución de sketches +* FreeCAD 006-01: Pieza solucionada con Dar espesor a un sólido +* FreeCAD 006-02: Continuación +* FreeCAD 007: Pieza de los 200 ejercicios +* FreeCAD 008: Simetrias y multitransformación en Part Design +* FreeCAD 009: Pantalones de caldereria +* FreeCAD 009_bis: Planos técnicos con Techdraw +* FreeCAD 010: Una T de fontanería +* FreeCAD 011: Enlazar diseño paramétrico con una hoja de datos +* FreeCAD 012: Caja con tornillos +* FreeCAD 013: Extruir caras de nuestros sólidos +* FreeCAD 014: Uso de imágenes para crear proyectos +* FreeCAD 015: Primera pieza de la cizalla +* FreeCAD 016: Cuchilla de la cizalla hidráulica +* FreeCAD 017: Más cuchilla +* FreeCAD 018: Materiales +* FreeCAD 019: Pasadores para la cizalla +* FreeCAD 020: Más cizalla +* FreeCAD 021: Todavía más cizalla +* FreeCAD 022: Algunos trucos en sketcher +* FreeCAD 023: Tiralineas +* FreeCAD 024: +* FreeCAD 025: +* FreeCAD 026: +* FreeCAD 027: +* FreeCAD 028: +* FreeCAD 029: +* FreeCAD 030: +* FreeCAD 031: +* FreeCAD 032: +* FreeCAD 033: +* FreeCAD 034: +* FreeCAD 035: +* FreeCAD 036: +* FreeCAD 037: +* FreeCAD 038: +* FreeCAD 039: + +### MangoJelly + +- [FreeCAD: Learn Python](https://invidious.snopyta.org/playlist?list=PLWuyJLVUNtc15o92Bo6SgtYzXt7zlSIsh) ver también [doc](https://wiki.freecadweb.org/Power_users_hub) +- [FreeCAD 6 for Beginners](https://invidious.snopyta.org/playlist?list=PLWuyJLVUNtc0UszswD0oD5q4VeWTrK7JC) + +### Misc + +- [Apuntes de Federico Coca](https://fgcoca.github.io/Mis-notas-de-FreeCAD/6-TD/) diff --git a/content/posts/notes_general/notes_gemini.md b/content/posts/notes_general/notes_gemini.md new file mode 100644 index 0000000..472876c --- /dev/null +++ b/content/posts/notes_general/notes_gemini.md @@ -0,0 +1,28 @@ +--- +weight: 4 +title: "Apuntes del protocolo Gemini" +date: 2021-11-02T12:43:07+0100 +draft: true +summary: "Protocolo Gemini" +categories: + - notes +tags: + - gemini +--- + + +{{< admonition type=warning title="Work in progress" open=true >}} + +No está completo. + +{{< /admonition >}} + +## Referencias + +- +- +- + + + + diff --git a/content/posts/notes_general/notes_generative_art.md b/content/posts/notes_general/notes_generative_art.md new file mode 100644 index 0000000..e5fd80a --- /dev/null +++ b/content/posts/notes_general/notes_generative_art.md @@ -0,0 +1,198 @@ +--- +weight: 4 +title: "Apuntes de Arte Generativo" +date: 2023-02-01T11:00:40+0100 +draft: false +summary: "Pues eso, apuntes sueltos de arte generado por ordenador" +categories: +- notes +tags: +- python +- processing +- py5 +- openFrameworks +- generative art +--- + + + +{{< admonition type=warning title="EN CONSTRUCCION" open=true >}} + +Esta página está muy lejos de estar completa, solo son apuntes sueltos para uso personal. + +{{< /admonition >}} + + +{{< admonition type=info title="Referencias" open=true >}} +- [Processing](https://processing.org/) +- [py5](http://py5coding.org/) un port de Processing para Python +- [py5 source](https://github.com/py5coding/py5) +- [Install py5](https://py5coding.org/content/install.html) +- [Generative Design Landing Page](http://www.generative-gestaltung.de/ "A landing page for the book") Un libro de arte generativo con muchos ejemplos de ___Processing___ +- [The Nature of Code](https://natureofcode.com/ "The book from Daniel Shiffman") Un libro de Daniel Shiffman que se puede leer on-line. Simulaciones de procesos físicos con ___Processing___ +- [Portando todos los ejemplos de Processing a py5 por Alexandre Villares](https://github.com/villares/py5examples/tree/processing-python-mode-examples/examples-from-Processing-Python-mode) +- [Structure Synth](https://structuresynth.sourceforge.net/) +- [Design for Complexity with Structure Synth](https://www.shapeways.com/blog/archives/32934-tutorial-tuesday-27-design-complexity-structure-synth.html) +- [Scripting in Structure Synth](http://blog.hvidtfeldts.net/index.php/2010/11/scripting-in-structure-synth/) +- [Context Free Art](https://www.contextfreeart.org/) +- [Generative Design](https://github.com/generative-design) The book on Github +- [Generative Arts Links](http://blog.hvidtfeldts.net/index.php/generative-art-links/) +{{< /admonition >}} + + +## py5, un port de Processing a Python + +### Requisitos + +Para que todo funcione correctamente necesitamos: + +- Python 3.8 +- Java 17 +- La biblioteca de gráficos _Cairo_ (esta es opcional, y yo ya la tenía instalada con `apt install libcairo2-dev`) + +### Instalación + +1. Instalamos la última versión de Python 3.8 (por precaución no instalo en la última versión de Python pero puedes probar) + +```bash +pyenv install --list |grep 3.8. +pyenv install 3.8.15 +``` + +2. Creamos un entorno virtual + +```bash +pyenv virtualenv 3.8.15 ve_py5 +``` + +3. Activamos el entorno e instalamos py5 + +```bash +pyenv ve_py5 activate +myve +pip install 'py5[jupyter]' +pip install cairosvg +``` + +4. Instalamos java mediante python (asegúrate de que sigues en el mismo virtualenv) + +```bash +pip install install-jdk +python -c "import jdk; print('Java installed to', jdk.install('17'))" +``` + +Con esto ya tenemos todo instalado. Podemos probar `py5` con un programa sencillo: + +```python3 +import py5 + +def setup(): + py5.size(200, 200) + py5.rect_mode(py5.CENTER) + +def draw(): + py5.rect(py5.mouse_x, py5.mouse_y, 10, 10) + +py5.run_sketch() + +``` + +### Java 17 + +Yo uso Openjdk en mi linux: + +```bash +java -version + +openjdk version "11.0.17" 2022-10-18 +OpenJDK Runtime Environment (build 11.0.17+8-post-Ubuntu-1ubuntu222.04) +OpenJDK 64-Bit Server VM (build 11.0.17+8-post-Ubuntu-1ubuntu222.04, mixed mode, sharing) +``` + +El Java 17 que hemos instalado se queda en `~/.jdk`. + +`py5` no necesita configuración adicional para usar el Java 17, sabe donde tiene que buscarlo. Si por alguna razón quieres usar esa versión de Java para otras historias puedes añadir un alias, como el siguiente, al fichero de alias (`~/.zalias.zsh`) + +```zsh +# Java 17 installed in ~/.jdk +java17() { + export JAVA_HOME="$HOME/.jdk" + export PATH="$HOME/.jdk/jdk-17.0.6+10/bin:$PATH" + java -version +} +``` + +Con ese alias podremos activar el Java 17 en nuestra sesión de terminal sin más que ejecutar `java17` + + +### Modos de funcionamiento de _py5_ + +#### Modo _module_ + +Este se parece bastante al modo clásico de _Processing_. En este modo podemos crear las funciones `settings`, `setup` y `draw`. Ninguna es obligatoria pero en la práctica es raro que no necesites `setup` y si quieres algo dinámico te hará falta `draw`. + +Un ejemplo típico de programa en modo _module_: + +```python3 +import py5 + +def setup(): + py5.size(300, 200) + py5.rect_mode(py5.CENTER) + +def draw(): + py5.rect(py5.mouse_x, py5.mouse_y, 10, 10) + +py5.run_sketch() +``` + +En `setup` podemos invocar funciones de `settings` y ahorrarnos escribir esa función, pero todo lo que pertenezca a `settings` tiene que ir al principio de la función `setup`. + +{{< admonition type=danger title="import py5" open=true >}} +Ni se te ocurra hacer `from py5 import *` tendrás problemas con varias cosas. __py5__ no está programado para soportar este tipo de _import_ que, en todo caso, siempre es poco aconsejable. +{{< /admonition >}} + +#### Modo _Class_ + +Nos permite crear Clases que heredan del objeto "_Sketch_" de __py5__. + +#### Modo _Imported_ + +Imita el funcionamiento de __Processing__, sin imports ni prefijos en las funciones de __py5__. Sólo podemos usar este modo desde __Jupyter__ tras instalar el _kernel_ de __py5__ + +#### Modo _Static_ + +Para crear imágenes estáticas + + + +## openFrameworks + +Nos bajamos los fuentes para linux 64bits desde [la página web del proyecto](https://openframeworks.cc), y las descomprimimos en un directorio para proceder a compilarlas. + +No hay más que seguir [las instrucciones de instalación para linux](https://openframeworks.cc/setup/linux-install/). + +La instalación no es demasiado intrusiva si tienes Ubuntu 18 o mayor y una versión reciente del gcc. + +Al instalar las dependencias añadimos los siguientes paquetes a nuestro sistema: + +~~~~bash +installing OF dependencies +OF needs to install the following packages using apt-get: +curl libjack-jackd2-0 libjack-jackd2-dev freeglut3-dev libasound2-dev libxmu-dev libxxf86vm-dev g++ libgl1-mesa-dev libglu1-mesa-dev libraw1394-dev libudev-dev libdrm-dev libglew-dev libopenal-dev libsndfile-dev libfreeimage-dev libcairo2-dev libfreetype6-dev libssl-dev libpulse-dev libusb-1.0-0-dev libgtk-3-dev libopencv-dev libassimp-dev librtaudio-dev libboost-filesystem-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-libav gstreamer1.0-pulseaudio gstreamer1.0-x gstreamer1.0-plugins-bad gstreamer1.0-alsa gstreamer1.0-plugins-base gstreamer1.0-plugins-good gdb libglfw3-dev liburiparser-dev libcurl4-openssl-dev libpugixml-dev libgconf-2-4 libgtk2.0-0 libpoco-dev +~~~~ + +No te olvides de compilar también el _Project Generator_. + + + +## Structure Synth + +Instalamos con `sudo apt install structure-synth sunflow` + +## Context Free Art diff --git a/content/posts/notes_general/notes_git.md b/content/posts/notes_general/notes_git.md new file mode 100644 index 0000000..55daf9e --- /dev/null +++ b/content/posts/notes_general/notes_git.md @@ -0,0 +1,260 @@ +--- +weight: 4 +title: "Apuntes de Git" +date: 2022-07-28T20:30:52+0200 +draft: false +summary: "Pues eso, apuntes sueltos de git" +categories: +- notes +tags: +- git +--- + + +## Detached-HEAD + +Lo normal es que te enteres después de hacer uno o varios *commits* en *Detached* + +### Con rama temporal + +```bash +git branch tmp +git checkout main +git merge tmp +git branch -d tmp +``` + +### Directamente + +```bash +git checkout main +git merge HEAD@{1} +``` + +## Dejar de seguir un fichero + +### Sin borrarlo del directorio de trabajo + +1. Primero añadimos el fichero o directorio que quieres ignorar al .gitignore. +2. Ejecutamos: + + ```bash + git rm -r --cached + git add . + ``` + + Vale para ficheros o directorios, la opción `-r` es para hacerlo recursivo, la opción `--cached` esta para que no borre nada en el _working tree_ solo borra en _index_ + + __OJO__: Este método borra los ficheros para el resto del equipo cuando hagan un `git pull` + +### Queremos hacer modificaciones del fichero en local sin propagarlas al resto del equipo + +```bash +git --update-index --skip-worktree +``` + + + +## Hacer un merge + +1. Asegúrate de completar todas las tareas para dejar lista la rama que vas a fusionar +2. Asegúrate de cambiár a la rama receptora (`git checkout`) y de que estás en ella (`git status`) +3. Actualiza la rama receptora con el _remote_ (puedes usar `git fetch` y `git pull`) +4. Ejecuta el _merge_ con `git merge` + +## Borrar submódulo + +La forma correcta de hacerlo, conservando el contenido del módulo si es necesario. + +```bash +mv a/submodule a/submodule_tmp # keeps a backup of module contents + # do this only if keeping the contents + +git submodule deinit -f a/submodule # removes submodule from repo +rm -rf .git/modules/a/submodule # removes submodule reference +git rm -f a/submodule # Note: a/submodule (no trailing slash) + +# or, if you want to leave it in your working tree and have done step 0 +git rm --cached a/submodule +mv a/submodule_tmp a/submodule +``` + + +## Clonar cuando hay submódulos + +La opción `-jn` asigna n recursos para las tareas de clonado. + +```bash +git clone --recursive -j8 git://github.com/foo/bar.git +``` + +## Deshacer un _amend_ + +Siempre y cuando no la hayas pifiado ya en la historia compartida. + +```bash +git reset --soft HEAD@{1} +git commit -C HEAD@{1} +``` + +La primera orden mueve el _HEAD_ a donde estaba apuntando el _commit_ antiguo. Además dejamos el _index_ intacto para poder hacer otra vez un _commit_ con los cambios pendientes. + +`HEAD@{1}` nos da el _commit_ al que _HEAD_ estaba apuntando antes de apuntar al que está apuntando (léelo un par de veces más). No es lo mismo que `HEAD~1` que devuelve el _commit_ padre del _commit_ al que está apuntando _HEAD_. + +La segunda sentencia es más retorcida. Significa: _haz un commit del tree actual, usando los detalles del commit erroneo_. Fíjate que `HEAD@{1}` ahora apunta al _commit_ erróneo, puesto que apunta al _commit_ al que apuntaba _HEAD_ antes de apuntar a donde apunta ahora mismo. + +## _Revert_ a un _commit_ previo + +### _Detached Head_ + +```bash +git checkout +``` + +__¡Ojo!__ ahora mismo estás en un _detached head_ es decir no estás en ningún _branch_. Si haces cambios en el _commit_ `` vas a tener muchos problemas. Antes de hacer cambios puedes crear una nueva rama: + +```bash +git switch -c +``` + +O volver a donde estabas con `git checkout` + +### Con una rama + +Si quieres hacer cambios es lo más recomendable: + +```bash +git checkout -b old-state +``` + +### A fuego (quemando las naves) + +__Si no tienes nada en la historia compartida__ puedes hacer reset: + +```bash +# Perderás todos los cambios locales! +git reset --hard + +# Si quieres guardar cambios pendientes +git stash +git reset --hard +git stash pop +# Salvar modificaciones y reaplicar +``` + + +__Si ya has compartido la historia__ tendrás que trabajar con _revert_. + +Esta también es la forma más segura y recomendable si quieres conservar la historia completa: + +```bash +git revert --no-commit ..HEAD +git commit +``` + +El _flag_ `--no-commit` permite hacer un _revert_ de todos los _commits_ de un golpe, si no lo pasas hará un nuevo _commit_ por cada _commit_ "revertido" y la historia va a quedar un poco enrevesada. + +## Cambiar el nombre a una rama + +En _Gitlab_ hay que usar _main_ en lugar de _master_. + + +```bash +git branch -m +git push origin : +git push --set-upstream origin +``` + + +## Cambiar de master a main (otro método) + +En local: + +```bash +git branch -m master main +git status +git push -u origin main +git push origin --delete master +``` + +Resto del equipo: + +```bash +git checkout master +git branch -m master main +git fetch +git branch --unset-upstream +git branch -u origin/main +``` + + + +## Crear una nueva rama + +```bash +git branch # Crea una nueva rama +git checkout # Cambia a la nueva rama +git switch # También cambia a la nueva rama +git push -u origin # Sube la nueva rama al repo remoto +``` + +Hay otras alternativas: + +```bash +git branch ffeeddaa # Crea una rama a partir de un commit +git branch v1.2 # Crea una rama a partir de un tag +git branch --track origin/ # Crea una rama a partir de una rama remota +``` + +## Borrar un fichero con información sensible de la historia del repo + + +### Usando solo git +```bash +git filter-branch --force --index-filter \ + "git rm --cached --ignore-unmatch PATH-TO-YOUR-FILE-WITH-SENSITIVE-DATA" \ + --prune-empty --tag-name-filter cat -- --all +git push --force --verbose --dry-run +git push --force +``` + +### Usando git-filter-repo + +Es una herramienta escrita en Python (ver [github](https://github.com/newren/git-filter-repo)) + + +## rebase interactive + +- + + +## Escribir mensajes de commit significativos + +- + +**Mis tipos de _commit_** + +Para usar con este formato: + +```bash +[optional scope]: + +[optional body] + +[optional footer(s)] +``` + + +| Type | Tipo | Descripción | +|:--|:--|:--| +| dev | desarrollo | Es un _commit_ de desarrollo en las fases iniciales del proyecto | +| fix | fix | Se arregla un _bug_ | +| chore | mantenimiento | El _commit_ no implica ni cambios de código ni de test | +| refactor | | refactorización de código que no arregla un _bug_ ni añade features | +| docs | | Añade o retoca documentación del proyecto | +| style | | Cambios que no afectan el significado del código, probablemente relacionados con el formateado del código, y similares | +| test | | Crea nuevos test o corrige test existentes | +| perf | | mejoras de _performance_ | +| ci | | Relacionados con integración contínua (_continuous integration_) | +| build | | cambios que afecta al _build_ del proyecto, p.ej. cambios de dependencias | +| revert | | Vuelta a un _commit_ previo | diff --git a/content/posts/notes_general/notes_golang.md b/content/posts/notes_general/notes_golang.md new file mode 100644 index 0000000..3995b8e --- /dev/null +++ b/content/posts/notes_general/notes_golang.md @@ -0,0 +1,1783 @@ +--- +weight: 4 +title: "Apuntes del lenguaje de programación Go" +date: 2021-05-18T09:55:41+0200 +draft: false +summary: "Apuntes de Go" +categories: + - notes +tags: + - golang + - programacion +--- + +{{< admonition type=warning title="Work in progress" open=true >}} + +Apuntes incompletos del lenguaje de programación Go + +{{< /admonition >}} + + + +## Referencias + +- [Documentación Oficial](https://godoc.org/) +- [CodeReviewComments](https://github.com/golang/go/wiki/CodeReviewComments) +- [Golang 101 hacks](https://nanxiao.gitbooks.io/golang-101-hacks/content/) +- [Un video curso de Traversy Media](http://www.tonvid.com/info.php?video_id=SqrbIlUwR0U) +- [Un video curso de 7 horas de freeCodeCamp](http://www.tonvid.com/info.php?video_id=YS4e4q9oBaU) +- [Go Workshop Book](https://subscription.packtpub.com/book/ppprogramming/9781838647940) +- [Awesome Go](https://awesome-go.com/) +- [Golang by example: All design patterns in Go](https://golangbyexample.com/all-design-patterns-golang/) +- [Design Patters](https://refactoring.guru/design-patterns/go) (lo mismo pero con más dibujitos) +- [Go Modules](https://go.dev/blog/using-go-modules) +- [Build a web app in Golang](https://astaxie.gitbooks.io/build-web-application-with-golang/content/en/01.1.html) + +### Books +- Esencia del lenguaje Go: ["The Go Programming Language" Addison-Wesley](https://www.amazon.es/dp/0134190440/ref=cm_sw_r_cp_apa_fabc_7-7WFbSZRP6WG) +- Panorámica del go-stdlib: ["Mastering Go" Packt Publishing; 2nd Revised edition](https://www.amazon.es/dp/1838559337/ref=cm_sw_r_u_apa_i_DB8VXX0DNT6NPRMRAMHJ) + +## Instalación + +- Bajamos el paquete con la última versión desde la [página oficial](https://go.dev/dl/). +- Y lo descomprimimos como root en `/usr/local/` + +```bash +cd ~/tmp/go +wget https://go.dev/dl/go1.17.5.linux-amd64.tar.gz +sudo cp /usr/local/go /usr/local/go.bkp +sudo tar -C /usr/local -xvzf go1.17.4.linux-amd64.tar.gz +``` + +Después de instalar es importante ajustar las rutas en `.profile` (ver la explicación en el siguiente punto): + +```bash +# golang +if [ -d "/usr/local/go" ] ; then +# export GOROOT="/usr/local/go" +# PATH="$PATH:$GOROOT/bin" +fi +if [ -d "$HOME/go" ] ; then + export GOPATH="$HOME/go" + PATH="$PATH:$GOPATH/bin" +fi + +export PATH +``` + +{{< admonition type=danger title="GOROOT" open=true >}} + +Varios autores ([un ejemplo](https://dave.cheney.net/2013/06/14/you-dont-need-to-set-goroot-really)) recomiendan **NO** establecer el valor de `GOROOT` cuando usamos la localización estándar. Así que he comentado esa parte en mi fichero `~/.profile` + +{{< /admonition >}} + + + +### Configuración del entorno de Go + +{{< admonition type=info title="Referencias" open=false >}} +- [Golang Doc: gopath_code](https://golang.org/doc/gopath_code) +- [Golang Enviroment Configuration](https://www.metaltoad.com/blog/golang-environment-configuration) +- [Build a Web Application with Golang](https://astaxie.gitbooks.io/build-web-application-with-golang/content/en/01.0.html) +{{< /admonition >}} + +Para dejar configurado el entorno de trabajo en _Go_ conviene conocer las siguientes variables de entorno (aunque hay [más](https://golang.org/doc/)): + +**GOROOT** + +: Es el directorio donde se localiza la biblioteca estándar de _Go_. Por defecto _Go_ asume la ruta `/usr/local/go`, así que estrictamente hablando **NO** es necesario establecer esta variable si has instalado en `/usr/local`. (Con [retoques de esta variable](https://golang.org/doc/manage-install) es posible instalar varias versiones de _Go_) + +**GOBIN** + +: Es el directorio donde quedan instalados los binarios de _Go_ cuando ejecutamos un comando `go install ...`. Solo deberías tocar esta variable si no quieres instalar los binarios en la localización por defecto: `$GOPATH/bin`. + +**GOOS** + +: especifica el sistema operativo, es opcional. En mi caso tiene valor `linux` + +**GOARCH** + +: especifica la arquitectura del procesador de nuestra máquina, también es opcional, en mi caso es `amd64` + +**GOPATH** + +: La ruta a nuestro directorio de trabajo (nuestro _workspace_). Esta variable es **obligatoria** y **no tiene valor por defecto**, podeis ponerla en donde tenga sentido para vosotros, por ejemplo `/home/salvari/code/go` + +Evidentemente tenemos que dejar las variables de entorno exportadas y añadir al `PATH` de nuestro usuario las que correspondan (ver punto anterior) + +{{< admonition type=tip title="Ver el entorno" open=true >}} + +Puedes ver el entorno completo de Go con el comando `go env` + +{{< /admonition >}} + + +#### Más de GOPATH + +{{< admonition type=warning title="Abandonando GOPATH" open=true >}} + +Estamos usando ya go 1.17. Hay planes firmes para abandonar el tratamiento de dependencias via `GOPATH` y centrarse exclusivamente en gestionarlas a través de módulos. + +Todo lo que comento aquí de `GOPATH` hay que tratarlo con pinzas. + +{{< /admonition >}} + + +_Go_ espera que en el directorio `$GOPATH` haya tres subdirectorios: + +```bash +bin/ +pkg/ +src/ +``` + +- `bin/` contiene los ejecutables que se generan cuando ejecutamos `go install ...` +- `pkg/` contiene los paquetes instalados en nuestro sistema con `go get ...` +- `src/` **aquí es donde deberíamos alojar nuestro código fuente** (no es obligatorio) + +Como hemos dicho en `$GOPATH/src/` es donde tenemos que programar, ahí es donde tiene que estar nuestro código fuente, pero ojo la ruta exacta de nuestros proyectos es función de nuestra plataforma de control de código, y tiene que tener la forma `Source-Control-Platform/User/Repository`. + +Por ejemplo podríamos tener los siguientes proyectos en el directorio `src/`: +```bash +src/github.com/salvari/miAppGo +src/gilab.com/salvari/mach5 +``` + +### Instalación de herramientas + +#### _golint_ y _godoc_ + +**Ya no** instalamos con los comandos: + +```bash +go get -u golang.org/x/lint/golint +go get -u golang.org/x/tools/cmd/godoc +``` + +El comando `go get` se usa exclusivamente para añadir dependencias al módulo que estemos creando. + +Los binarios que queremos usar en nuestro sistema se instalan con el nuevo estilo de instalación [recomendado](https://go.dev/doc/go-get-install-deprecation) desde go v1.17: + +```bash +go install golang.org/x/lint/golint@latest +go install golang.org/x/tools/cmd/godoc@latest +``` + +Alternativamente podemos instalar **todas** las herramientas de _Go_ con: + +```bash +go get -u golang.org/x/tools/... +``` + +Pero con esta opción me falla la instalación de `gopls`, una herramienta que necesito en mi configuración de _Emacs_ + +#### _gopls_ para protocolos LSP en editores + +Desde un directorio **que no sea el GOPATH** + +```bash +# GO111MODULE=on # not needed with 'go install' +go install golang.org/x/tools/gopls@latest +``` + +#### Herramientas adicionales + +- gopkgs +- go-outline +- dlv +- dlv-dap +- staticcheck + +```bash +go install github.com/uudashr/gopkgs/v2/cmd/gopkgs@latest +go install github.com/ramya-rao-a/go-outline@latest +go install github.com/go-delve/delve/cmd/dlv@latest +go install github.com/go-delve/delve/cmd/dlv@master +go install honnef.co/go/tools/cmd/staticcheck@latest +go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest +``` + +### Alias para zsh + +```zsh +# golang aliases +alias gob='go build' # Build your code +alias goc='go clean' # Removes object files from package source directories +alias god='go doc' # Prints documentation comments +alias gof='go fmt' # Gofmt formats (aligns and indents) Go programs. +alias gofa='go fmt ./...' # Run go fmt for all packages in current directory, recursively +alias gog='go get' # Downloads packages and then installs them to $GOPATH +alias goi='go install' # Compiles and installs packages to $GOPATH +alias gol='go list' # Lists Go packages +alias gom='go mod' # Access to operations on modules +alias gop='cd $GOPATH' # Takes you to $GOPATH +alias gopb='cd $GOPATH/bin' # Takes you to $GOPATH/bin +alias gops='cd $GOPATH/src' # Takes you to $GOPATH/src +alias gor='go run' # Compiles and runs your code +alias got='go test' # Runs tests +alias gov='go vet' # Vet examines Go source code and reports suspicious constructs +``` + +## Go Modules + +Básicamente un módulo (_Go Module_) es una colección de paquetes (_packages_) almacenados en un arbol de directorios que tiene un fichero `go.mod` en su raiz. El fichero `go.mod` especifica el _module path_ que es la ruta canónica al módulo que se usará para importarlo, y todas las depencias requeridas por el módulo, es decir el conjunto de módulos (otros módulos) que serán necesarios para compilar con éxito este módulo. Cada dependencia se especifica con su correspondiente _module path_ y un número de versión semántica (_semantic version_) + +### Crear un módulo + +El comando para crear un módulo es: `go mod init` + +Antiguamente el compilador distinguía dos formas de funcionamiento, el _go module mode_ y el _gopath mode_. A estas alturas tenemos que usar el _go module mode_ el otro está en desuso. De hecho el compilador ahora funciona en _go module mode_ por defecto. + +Si estamos en un directorio por debajo del `$GOPATH` nuestro directorio de proyecto debería tener esta forma: + +```bash +$GOPATH/src/example.com/$USER/moduleName +``` + +En este caso el comando `go mod init` no necesita parámetros, genera un _module path_ basado en la estructura de directorios. + +Si estamos trabajando en un directorio fuera del `$GOPATH` tendremos que especificar el _module path_ en el comando: + +```bash +go mod init example.com/modulename +``` + +### Actualizar el fichero `.mod` + +Ejecutamos: `go mod tidy` + + +## TDD (_Test Drived Development_) en Go + +{{< admonition type=info title="Referencias" open=true >}} + +- [Learn Go with Tests](https://quii.gitbook.io/learn-go-with-tests/) Un buen sitio para empezar +- [An introduction to Testing in Go](https://tube.cthd.icu/watch?v=GlA57dHa5Rg) Un video muy ilustrativo +- [Advanced Testing in Go](https://tube.cthd.icu/watch?v=S1O0XI0scOM) Continuación del anterior + + +{{< /admonition >}} + +TDD es una buena práctica: +- Mejora la calidad del código +- En general hace que escribamos un código más desacoplado (tiendes a aislar más las cosas para facilitar los test) +- Tiene la ventaja obvia de tener todo controlado con test fáciles de repetir +- Bien escritos valen como documentación de bajo nivel +- Previene regresiones (fallos que reaparecen al avanzar en el desarrollo) +- Potencia la arquitectura y diseño modulares + +### Tres reglas ### + +Se suele hablar del ciclo: ""rojo, verde y refactorizar" (_red, green and refactor_) + +- Primero hay que escribir un test que falla +- Hay que programar lo justo para que el test no falle. No se puede escribir más código que el necesario para que el test no falle. +- Se revisa el código para refinarlo (tanto el código producto como el código de los test) + + +Los pasos que nos proponen en la página de _Learn Go with Test_: +- Escribe un test +- Haz lo necesario para que el compilador pase sin errores +- Ejecuta el test, comprueba que falla y que el mensaje de fallo es significativo +- Escribe el código suficiente para que el test no falle +- Refina el código (_Refactor_) + +Nos dicen literalmente: + + Este ciclo puede parecer tedioso pero es importante mantenerlo. + + Al hacerlo no solo te aseguras de tener test significativos, sino que aseguras un buen diseño del software respaldado por la seguridad que dan los test. + + Ver fallar el test es un paso importante por qué permite comprobar el mensaje de error (que debe ser significativo) Como desarrollador puede ser muy difícil trabajar con código cuando los mensajes de fallo de los test no dan una idea clara de que está pasando. + + Asegurándote de que los test se ejecutan rápido y estableciendo un entorno de trabajo que facilite escribir los test puedes "entrar en sintonía" y programar de la forma más eficiente posible. + + Si no escribes los test, te verás obligado a comprobar el funcionamiento del código ejecutándolo manualmente, eso rompera tu concentracion y a la larga te hara perder bastante mas tiempo que escribir correctamente los test. + +### Sintáxis para test + +Escribir un test es como escribir una función pero: +- Tiene que estar en un fichero de la forma `xxx_test.go` +- El nombre de la función que implementa el test tiene que empezar por `Test` +- La función que implementa el test tiene que tener un único argumento: `t *testing.T` +- Para poder usar ese tipo de argumento y otras facilidades de testeo es necesario hacer `import "testing"` + + +### Código independiente es más fácil de testear ### + +En el ejemplo _Hello World_ es mejor escribir una funcion que devuelve la cadena con el saludo que una función que escribe el saludo. Por que así somos independientes de la salida que puede ser por pantalla, por una página web, por un mensaje, escrita, etc. etc. Y de paso es más simple de testear. + + +## Go + +`_` + +: Es el _black identifier_ podemos usarlo para pasar de variables que + no vamos a usar. + + ```golang + a = "Una cadena" + for _, r := range a { + fmt.Println(r) + } + + ``` + +`make` + +: Parece que vale para hacer un _alloc_ de memoria. + + ```golang + counts := make(map[string]int) + ``` + +`time` + +: La especificación de formatos tiene su gracia ¬_¬ [ver referencia](https://golang.org/src/time/format.go) + +### Arrays + +```go +var myArray1 = [3]int // will be filled with so called + // zero values, for integers: 0 + +var myArray2 = [5]int{1,2,3,4,5} // number of values between { } can + // not be larger than size (ofc) + +var myArray3 = […]int{1,2,3,4} // the compiler will count the + // array elements for you +``` + +- [Diferencias entre array y slice](https://gist.github.com/josephspurrier/b5713f9a534afe3cfdd2) +- [The Geek Stuff: Golang Array vs Slice](https://www.thegeekstuff.com/2019/03/golang-slice-examples/) +- [A comprehensive guide on array and slices in Go](https://www.sohamkamani.com/golang/arrays-vs-slices/) +- [Go: Array vs Slices Bonanza](https://medium.com/@marty.stepien/arrays-vs-slices-bonanza-in-golang-fa8d32cd2b7c) +- [Slice Tricks by Ian Lance Taylor](https://github.com/golang/go/wiki/SliceTricks) +- [DigitalOcean: Understanding array and slices in Go](https://www.digitalocean.com/community/tutorials/understanding-arrays-and-slices-in-go) + + +## Go Cheat Sheet + +Copia descarada de [esto](https://github.com/a8m/golang-cheat-sheet) + + +### Crédito + +La mayor parte de lo que se cuenta en esta sección está copiado de [golang-cheat-sheet](https://github.com/a8m/golang-cheat-sheet) + +Los créditos originales avisan de que la mayor parte de los ejemplos tomados de [A Tour of Go](http://tour.golang.org/), una excelente introducción al lenguage __Go__ + + +### Go en resumen + +* Lenguaje Imperativo +* Tipado estático +* Sintáxis muy similar a la de C (pero con menos paréntesis y sin punto y coma obligatorio) la estructura es parecida a la de Oberon-2 +* Compila a código nativo (nada de vm) +* No hay clases, pero tiene _structs_ con métodos +* Interfaces +* No implementa herencia. Hay algo llamado [type embedding](http://golang.org/doc/effective%5Fgo.html#embedding) +* Las Funciones son ciudadanos de primera clase +* Las Funciones pueden devolver múltiples valores +* Tiene _closures_ +* Hay Punteros pero sin aritmética de punteros +* La concurrencia es _"Built-in"_ mediante las primitivas: _Goroutines_ y _Channels_ + +### Sintáxis básica + +#### Hello World + +Fichero `hello.go`: + +```go +package main + +import "fmt" + +func main() { + fmt.Println("Hello Go") +} +``` + +`$ go run hello.go` + +### Operadores + +#### Aritméticos +|Operador|Descripción| +|--------|-----------| +|`+`|addition| +|`-`|subtraction| +|`*`|multiplication| +|`/`|quotient| +|`%`|remainder| +|`&`|bitwise and| +|`\|`|bitwise or| +|`^`|bitwise xor| +|`&^`|bit clear (and not)| +|`<<`|left shift| +|`>>`|right shift| + +#### Comparación +|Operador|Descripción| +|--------|-----------| +|`==`|equal| +|`!=`|not equal| +|`<`|less than| +|`<=`|less than or equal| +|`>`|greater than| +|`>=`|greater than or equal| + +#### Lógicos +|Operador|Descripción| +|--------|-----------| +|`&&`|logical and| +|`\|\|`|logical or| +|`!`|logical not| + +#### Otros +|Operador|Descripción| +|--------|-----------| +|`&`|address of / create pointer| +|`*`|dereference pointer| +|`<-`|send / receive operator (see 'Channels' below)| + +### Declaraciones +__El tipo va después del indentificador de variable__ +```go +var foo int // declaración sin inicialización +var foo int = 42 // declaración con inicialización +var foo, bar int = 42, 1302 // declaración e inicialización múltiples +var foo = 42 // se omite el tipo, será inferido +foo := 42 // abreviado, sólo es válido en el cuerpo de las funciones, + // el tipo siempre es implícito +const constant = "Esto es una constante" + +// iota se puede usar para números que se incrementan, empezando por cero +const ( + _ = iota + a + b + c = 1 << iota + d +) + fmt.Println(a, b) // 1 2 (0 is skipped) + fmt.Println(c, d) // 8 16 (2^3, 2^4) +``` + +### Funciones + +```go +// una función simple +func functionName() {} + +// función con parámetros (aquí también va el tipo después de la variable) +func functionName(param1 string, param2 int) {} + +// multiples parámetros del mismo tipo +func functionName(param1, param2 int) {} + +// podemos especificar el tipo devuelto por la función +func functionName() int { + return 42 +} + +// Pueden devolver multiples valores +func returnMulti() (int, string) { + return 42, "foobar" +} +var x, str = returnMulti() + +// si los valores devueltos tienen nombre no hace falta especificarlos en el Return +func returnMulti2() (n int, s string) { + n = 42 + s = "foobar" + // n and s will be returned + return +} +var x, str = returnMulti2() +``` + +#### Funciones como valores y _closures_ + +```go +func main() { + // asignar una función a una variable + add := func(a, b int) int { + return a + b + } + // usar el nombre para llamar a la función + fmt.Println(add(3, 4)) +} + +// 'Closures', lexically scoped: Las funciones pueden acceder valores que estában dentro del alcance (scope) +// cuando se definió la función + +func scope() func() int{ + outer_var := 2 + foo := func() int { return outer_var} + return foo +} + +func another_scope() func() int{ + // won't compile because outer_var and foo not defined in this scope + outer_var = 444 + return foo +} + + +// Closures +func outer() (func() int, int) { + outer_var := 2 + inner := func() int { + outer_var += 99 // outer_var from outer scope is mutated. + return outer_var + } + inner() + return inner, outer_var // return inner func and mutated outer_var 101 +} +``` + +#### Variadic Functions + +```go +func main() { + fmt.Println(adder(1, 2, 3)) // 6 + fmt.Println(adder(9, 9)) // 18 + + nums := []int{10, 20, 30} + fmt.Println(adder(nums...)) // 60 +} + +// Usando ... antes del nombre del tipo del último parámetro indicamos que la función acepta cero o mas parámetros de ese tipo +// La función se usa como cualquier otra, excepto que podemos pasar tantos parámetros como queramos del último parámetro definido +func adder(args ...int) int { + total := 0 + for _, v := range args { // Iterates over the arguments whatever the number. + total += v + } + return total +} +``` + +### Tipos _Built-in_ + +``` +bool + +string + +int int8 int16 int32 int64 +uint uint8 uint16 uint32 uint64 uintptr + +byte // alias for uint8 + +rune // alias for int32 ~= a character (Unicode code point) - very Viking + +float32 float64 + +complex64 complex128 +``` + +### Conversión de tipos + +```go +var i int = 42 +var f float64 = float64(i) +var u uint = uint(f) + +// alternative syntax +i := 42 +f := float64(i) +u := uint(f) +``` + +### Paquetes (_Packages_) + +{{< admonition type=info title="Referencias" open=false >}} +* [Golangbot: Go Packages](https://golangbot.com/go-packages/) +* [The Go blog: Using Go Modules](https://blog.golang.org/using-go-modules) +* [How to write Go code with GOPATH](https://golang.org/doc/gopath_code) +{{< /admonition >}} + +Las unidades de encapsulado en _Go_ de menor a mayor nivel serían: +- Funciones +- Paquetes +- Módulos + +Los paquetes nos permiten organizar los ficheros de código para hacerlos modulares y reutilizables además de facilitar el mantenimiento del software. + +* Cada fichero de código _Go_ debe pertenecer a un paquete. La pertenencia se declara con `package` al principio de cada uno de los ficheros fuente, evidentemente un paquete puede "poseer" varios ficheros. +* Los ejecutables están en el paquete `main`. Es un paquete especial. +* Por convención los programas ejecutables (los del paquete `main`) se llaman comandos (_commands_). El resto se llaman simplemente paquetes (_packages_) +* Por convención: == último nombre del _import path_ (import path `math/rand` => package `rand`) Lo normal es que todos los ficheros del paquete `rand` se guarden en el directorio `rand/` +* Si el identificador empieza con mayúscula: se exporta el símbolo (será visible desde otros paquetes) +* Si el identificador empieza con minúscula: privado (no será visible desde otros paquetes) + +Los **módulos** son coleciones de paquetes. Son imprescindibles para crear nuestros propios paquetes por qué la ruta de nuestros paquetes viene dada por el **módulo**. En cuanto quieras empezar a organizar tu código en paquetes inevitablemente tendrás que crear también **módulos**. + +El flujo típico de trabajo para un proyecto estructurado con _Packages_ sería el siguiente: + +- Supongamos que nuestro proyecto será un "pomodoro" y que va a gestionar _Timers_ y _Notifications_ (por decir algo) +- Creamos el directorio del proyecto que vamos a llamar `pomodoro`, dentro de ese directorio tendremos el _package main_ con los "comandos". + ```bash + mkdir pomodoro + cd pomodoro + touch main.go + ``` +- Iniciamos el modulo en la raiz del proyecto + ```bash + go mod init pomodoro + + # Aunque lo mas correcto sería iniciarlo como + go mod init gitlab/salvari/pomodoro + ``` +- Creamos los subdirectorios para los _packages_: `timer` y `notification`. Dentro de los subdirectorios creamos un fichero de código (pueden ser tantos ficheros como queramos) + ```bash + mkdir timer + touch timer/timer.go + mkdir notification + touch notification/notification.go + ``` + +Tendremos una estructura de directorios: + +```bash +pomodoro +├── go.mod +├── main.go +├── notification +│  └── notification.go +└── timer + └── timer.go +``` + +El contenido del fichero `go.mod` será (la versión de go depende de lo que tengas instalado): + +```go +module pomodoro + +go 1.17 +``` + +Cada fichero con extensión `.go` debe empezar **siempre** con la declaración del _package_. En nuestro caso `main.go` declarará `package main` y por ejemplo `notification.go` declarará `package notification`. + +Ya tenemos todo estructurado, ahora en nuestros ficheros de código podremos hacer _imports_ de este estilo + +```go +import "pomodoro/timer" +import "pomodoro/notification" +``` + +#### Gestión de dependencias con módulos en Go + + + + + +### Control del flujo de programa + +#### If + +```go +func main() { + // Basic one + if x > 10 { + return x + } else if x == 10 { + return 10 + } else { + return -x + } + + // You can put one statement before the condition + if a := b + c; a < 42 { + return a + } else { + return a - 42 + } + + // Type assertion inside if + var val interface{} + val = "foo" + if str, ok := val.(string); ok { + fmt.Println(str) + } +} +``` + +#### Bucles +```go + // Solo hay bucle `for`, no hay `while`, ni `until` + for i := 1; i < 10; i++ { + } + for ; i < 10; { // bucle while + } + for i < 10 { // se pueden omitir los ; si solo hay una condición + } + for { // Si omitimos la condición tenemos un while (true) + } + + // Podemos usar use break/continue en el bucle activo + // o usar break/continue con etiquetas (para bucles más externos) +here: + for i := 0; i < 2; i++ { + for j := i + 1; j < 3; j++ { + if i == 0 { + continue here + } + fmt.Println(j) + if j == 2 { + break + } + } + } + +there: + for i := 0; i < 2; i++ { + for j := i + 1; j < 3; j++ { + if j == 1 { + continue + } + fmt.Println(j) + if j == 2 { + break there + } + } + } +``` + +#### Switch + +```go + // switch statement + switch operatingSystem { + case "darwin": + fmt.Println("Mac OS Hipster") + // cases break automatically, no fallthrough by default + case "linux": + fmt.Println("Linux Geek") + default: + // Windows, BSD, ... + fmt.Println("Other") + } + + // al igual que con el 'for' y el 'if' podemos tener una sentencia de asignación justo antes de la variable del switch + switch os := runtime.GOOS; os { + case "darwin": ... + } + + // se pueden hacer comparaciones en los casos del switch + number := 42 + switch { + case number < 42: + fmt.Println("Smaller") + case number == 42: + fmt.Println("Equal") + case number > 42: + fmt.Println("Greater") + } + + // los casos pueden ser listas de valores separados por comas + var char byte = '?' + switch char { + case ' ', '?', '&', '=', '#', '+', '%': + fmt.Println("Should escape") + } +``` + +### Arrays, Slices, Ranges + +#### Arrays + +- Tienen longitud fija. Los _arrays_ de longitudes **son tipos diferentes** +- No pueden redimensionarse +- El índice empieza en cero +- Un array **no es un puntero** en Go + +```go +var a [10]int // declara un array de enteros (int) con longitud 10. ¡La longitud determina el tipo! +a[3] = 42 // establece el valor de un elemento +i := a[3] // lee el valor de un elemento + +// array literals +var a = [2]int{1, 2} +a := [2]int{1, 2} //shorthand +a := [...]int{1, 2} // elipsis -> El compilador infiere la longitud del array + +// no hay paso por referencia +arr1 := arr2 // Se hace una copia +arr1 := &arr2 // Se copia la referencia + +// multidimensionales +// var variable_name [SIZE1][SIZE2]…[SIZEN] variable_type + +a := [3][4]int{ + {0, 1, 2, 3} , // initializers for row indexed by 0 + {4, 5, 6, 7} , // initializers for row indexed by 1 + {8, 9, 10, 11} // initializers for row indexed by 2} +``` + +#### Slices + +- Un _slice_ es una abstracción apuntando a un array. +- Se pueden crear a partir de un array existente, en caso contrario Go creará el array detrás de las bambalinas +- No se especifica la dimensión +- Realmente un _slice_ son tres datos: + - Un puntero a la secuencia de datos en memoria + - Una longitud (_lenght_, `len(a)`) que almacena el número de elementos + - Una capacidad (_capacity_, `cap(a)`) que es el total de posiciones reservadas en memoria +- Cuando se asigna un _slice_ en realidad se copian esos valores, así que **se copia la referencia** +- Si un _slice_ tiene que crecer el compilador normalmente es conservador y **duplica** la capacidad. Eso implicará re-localizaciones del array subyacente en memoria + +```go +var a []int // declare a slice - similar to an array, but length is unspecified +var a = []int {1, 2, 3, 4} // declare and initialize a slice (backed by the array given implicitly) +a := []int{1, 2, 3, 4} // shorthand +chars := []string{0:"a", 2:"c", 1: "b"} // ["a", "b", "c"] + + +var b = a[lo:hi] // creates a slice (view of the array) from index lo to hi-1 +var b = a[1:4] // slice from index 1 to 3 +var b = a[:3] // missing low index implies 0 +var b = a[3:] // missing high index implies len(a) +a = append(a,17,3) // append items to slice a +c := append(a,b...) // concatenate slices a and b + +// create a slice with make +a = make([]byte, 5, 5) // first arg length, second capacity +a = make([]byte, 5) // capacity is optional + +// create a slice from an array +x := [3]string{"Лайка", "Белка", "Стрелка"} +s := x[:] // a slice referencing the storage of x +``` + +#### Operaciones sobre _Arrays_ y _Slices_ + +`len(a)` devuelve la longitud de un _array_ o _slice_ `a`. Es un _built-in_ no un atributo o método del *array*. + +```go +// iterar sobre un array o slice +for i, e := range a { + // i es el index, e es el elemento +} + +// si no vamos a usar el índice: +for _, e := range a { + // e es el elemento +} + +// si solo queremos usar el índice +for i := range a { +} + +// Desde Go 1.4 se puede iterar sin variables: +for range time.Tick(time.Second) { + // hacer algo cada segundo +} +``` + +### Maps + +- Los mapas (_maps_) son inmutables +- No se garantiza ningún orden + +```go +var m map[string]int +m = make(map[string]int) +m["key"] = 42 +fmt.Println(m["key"]) + +delete(m, "key") + +elem, ok := m["key"] // test if key "key" is present and retrieve it, if so + +// map literal +var m = map[string]Vertex{ + "Bell Labs": {40.68433, -74.39967}, + "Google": {37.42202, -122.08408}, +} + +// iterate over map content +for key, value := range m { +} + +``` + +### Structs + +No hay clases en __Go__, solo _structs_. Las _structs_ pueden tener métodos. + +```go +// Una struct es un tipo. También es una colección de campos + +// Declaración +type Vertex struct { + X, Y int +} + +// Creación +var v = Vertex{1, 2} +var v = Vertex{X: 1, Y: 2} // Creación de la struct definiendo sus valores con clave +var v = []Vertex{{1,2},{5,2},{5,5}} // Creación de un slice de structs + +// Acceso a miembros de la struct +v.X = 4 + +// Se pueden declarar métodos sobre structs. La referencia a la struct que recibe el método +// tiene que ir entre la palabra clave 'func' y el nombre del método +// LA ESTRUCTURA SE COPIA EN CADA LLAMADA AL MÉTODO +func (v Vertex) Abs() float64 { + return math.Sqrt(v.X*v.X + v.Y*v.Y) +} + +// Invocando el método +v.Abs() + +// Para los métodos que mutan la estructura usamos punteros a esa struct +// como tipos. De esta forma EVITAMOS LA COPIA de la estructura al invocar el método +func (v *Vertex) add(n float64) { + v.X += n + v.Y += n +} +``` + +Tenemos un caso especial con los contructores. Si queremos evitar que se construyan objetos sin usar el constructor: + +```go +package matrix + +type matrix struct { // NO EXPORTADO + .... +} +func NewMatrix(rows, cols int) *matrix { + m := new(matrix) + m.rows = rows + m.cols = cols + m.elems = make([]float, rows*cols) + return m +} + +``` + +Como no exportamos la estructura base, solo se pueden construir nuevos objetos a través del constructor. + +La función `NewMatrix` puede simplificarse fácilmente: + +```go +func NewMatrix(rows, cols, int) *matrix { + return &matrix{rows, cols, make([]float, rows*cols)} +} +``` + +{{< admonition type=warning title="structs y exportacion" open=true >}} +Si el nombre de la `struct` o de alguno de sus campos empieza por minúscula **no serán visibles** fuera de la propia `struct` +{{< /admonition >}} + +**Anonymous structs:** + +Más económicas y seguras que usar: `map[string]interface{}`. + +```go +point := struct { + X, Y int +}{1, 2} +``` + +### Punteros + +```go +p := Vertex{1, 2} // p es de tipo Vertex +q := &p // q es un puntero a un Vertex +r := &Vertex{1, 2} // r también es un puntero a un Vertex + +// El tipo de un puntero a un Vertex es *Vertex + +var s *Vertex = new(Vertex) // new crea un puntero a una nueva instancia de Vertex +``` + +### Interfaces + +- Un _interface_ es una especie de "contrato". +- Dentro del _interface_ especificamos las funciones que tiene que soportar un tipo para satisfacer el _interface_ +- El _interface_ nos da una capa extra de abstracción, podemos definir una función que procese o devuelva el "tipo" definido por el _interface_ + + +```go +// declaración +type Awesomizer interface { + Awesomize() string +} + +// los tipos no declaran en ningún sitio implementar un interface +type Foo struct {} + +// los tipos que implementan todos los métodos de un interface, satifacen ese interface implicitamente +func (foo Foo) Awesomize() string { + return "Awesome!" +} +``` + +### Embedding + +There is no subclassing in Go. Instead, there is interface and struct embedding. + +```go +// ReadWriter implementations must satisfy both Reader and Writer +type ReadWriter interface { + Reader + Writer +} + +// Server exposes all the methods that Logger has +type Server struct { + Host string + Port int + *log.Logger +} + +// initialize the embedded type the usual way +server := &Server{"localhost", 80, log.New(...)} + +// methods implemented on the embedded struct are passed through +server.Log(...) // calls server.Logger.Log(...) + +// the field name of the embedded type is its type name (in this case Logger) +var logger *log.Logger = server.Logger +``` + +### Errores + +No hay gestión de excepciones en __Go__. Las funciones que pueden lanzar un _Error_ simplemente devuelven un valor adicional de tipo `Error`. + +El _interface_ `Error` tiene esta pinta: +```go +type error interface { + Error() string +} +``` + +Una función que puede devolver un error: + +```go +func doStuff() (int, error) { +} + +func main() { + result, err := doStuff() + if err != nil { + // handle error + } else { + // all is good, use result + } +} +``` + +### Concurrency + +#### Goroutines + +Goroutines are lightweight threads (managed by Go, not OS threads). `go f(a, b)` starts a new goroutine which runs `f` (given `f` is a function). + +```go +// just a function (which can be later started as a goroutine) +func doStuff(s string) { +} + +func main() { + // using a named function in a goroutine + go doStuff("foobar") + + // using an anonymous inner function in a goroutine + go func (x int) { + // function body goes here + }(42) +} +``` + +#### Channels + +```go +ch := make(chan int) // create a channel of type int +ch <- 42 // Send a value to the channel ch. +v := <-ch // Receive a value from ch + +// Non-buffered channels block. Read blocks when no value is available, write blocks until there is a read. + +// Create a buffered channel. Writing to a buffered channels does not block if less than unread values have been written. +ch := make(chan int, 100) + +close(ch) // closes the channel (only sender should close) + +// read from channel and test if it has been closed +v, ok := <-ch + +// if ok is false, channel has been closed + +// Read from channel until it is closed +for i := range ch { + fmt.Println(i) +} + +// select blocks on multiple channel operations, if one unblocks, the corresponding case is executed +func doStuff(channelOut, channelIn chan int) { + select { + case channelOut <- 42: + fmt.Println("We could write to channelOut!") + case x := <- channelIn: + fmt.Println("We could read from channelIn") + case <-time.After(time.Second * 1): + fmt.Println("timeout") + } +} +``` + +##### Channel Axioms + +- A send to a nil channel blocks forever + + ```go + var c chan string + c <- "Hello, World!" + // fatal error: all goroutines are asleep - deadlock! + ``` +- A receive from a nil channel blocks forever + + ```go + var c chan string + fmt.Println(<-c) + // fatal error: all goroutines are asleep - deadlock! + ``` +- A send to a closed channel panics + + ```go + var c = make(chan string, 1) + c <- "Hello, World!" + close(c) + c <- "Hello, Panic!" + // panic: send on closed channel + ``` +- A receive from a closed channel returns the zero value immediately + + ```go + var c = make(chan int, 2) + c <- 1 + c <- 2 + close(c) + for i := 0; i < 3; i++ { + fmt.Printf("%d ", <-c) + } + // 1 2 0 + ``` + +### Printing + +```go +fmt.Println("Hello, 你好, नमस्ते, Привет, ᎣᏏᏲ") // basic print, plus newline +p := struct { X, Y int }{ 17, 2 } +fmt.Println( "My point:", p, "x coord=", p.X ) // print structs, ints, etc +s := fmt.Sprintln( "My point:", p, "x coord=", p.X ) // print to string variable + +fmt.Printf("%d hex:%x bin:%b fp:%f sci:%e",17,17,17,17.0,17.0) // c-ish format +s2 := fmt.Sprintf( "%d %f", 17, 17.0 ) // formatted print to string variable + +hellomsg := ` + "Hello" in Chinese is 你好 ('Ni Hao') + "Hello" in Hindi is नमस्ते ('Namaste') +` // multi-line string literal, using back-tick at beginning and end +``` + +### Reflection + +#### Type Switch + +A type switch is like a regular switch statement, but the cases in a type switch specify types (not values), and those values are compared against the type of the value held by the given interface value. + +```go +func do(i interface{}) { + switch v := i.(type) { + case int: + fmt.Printf("Twice %v is %v\n", v, v*2) + case string: + fmt.Printf("%q is %v bytes long\n", v, len(v)) + default: + fmt.Printf("I don't know about type %T!\n", v) + } +} + +func main() { + do(21) + do("hello") + do(true) +} +``` + +### Snippets + +#### HTTP Server + +```go +package main + +import ( + "fmt" + "net/http" +) + +// define a type for the response +type Hello struct{} + +// let that type implement the ServeHTTP method (defined in interface http.Handler) +func (h Hello) ServeHTTP(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "Hello!") +} + +func main() { + var h Hello + http.ListenAndServe("localhost:4000", h) +} + +// Here's the method signature of http.ServeHTTP: +// type Handler interface { +// ServeHTTP(w http.ResponseWriter, r *http.Request) +// } +``` + + +## Misc + +* Para ver el entorno usamos `go env` + + +## TDD con Go + +* [TDD with Go](https://leanpub.com/golang-tdd/read) + +Para que funcionen correctamente los comandos `go build`, `go clean` y `go test` parece que es necesario haber definido correctamente `go.mod` (con `go mod init`) + +`go test` buscará ficheros nombrados como `*_test.go` para ejecutarlos. + +Dentro de esos de test habrá bloques funcionales con la firma: `func TestXxxx(t *testing.T)` + +Los test fallidos se informan con `t.Errorf` + +### Ciclo de test + +El ciclo de trabajo se resume en _**Red, Green, Refactor**_. + +* Para implementar una nueva funcionalidad el primer paso debe ser escribir los test que capturan los requisitos de esa nueva funcionalidad. Estos test obviamente fallarán (_Red_ o rojo de fallo). +* La segunda fase consiste en implementar el código necesario para cumplir con los test y queden todos en verde (_Green_). El código debe minimizarse para cumplir los test sin "sobreimplementar" nada. +* El último paso es el _refactoring_. **No** implementamos nueva funcionalidad en este paso, se trata simplemente de hacer un código de calidad re-escribiendo el código de la segunda fase. **No** es un paso opcional, es imprescindible revisar el código y garantizar la coherencia y calidad del mismo. + +### Vicios en TDD +1. Escribir demasiados test a la vez +2. Concentrarse exclusivamente en los _happy path_ y la cobertura del código +3. No considerar los diferentes escenarios +4. Demasiadas comprobaciones (_asserts_) en un sólo test +5. Probar cosas diferentes en el mismo caso de prueba +6. Escribir test triviales para mantener el código cubierto +7. No ejecutar los test con frecuencia +8. No seguir siempre las tres fases. Especialmente implementando código sin escribir antes el test +9. Hacer _asserts_ que no prueban nada +10. Crear test difíciles de mantener + +### Dobles de prueba (_test doubles_) +Una implementación simplificada de algún tipo para facilitar las pruebas + +Casos posibles: + +_**Dummies**_ + +: Tipos sin ningún comportamiento, se implementan únicamente para cumplir con la firma de alguna función que queremos probar + +_**Stubs**_ ("breves") + +: Tipos que implementan el comportamiento mínimo para pasar un test + +***Mocks*** (parodias) + +: Implementaciones parciales que permiten definir como suponemos que serán los métodos sobre el tipo + +***Spies*** (espías) + +: Implementaciones parciales que nos permiten comprobar que métodos han sido invocados + +***Fakes*** (falsificaciones) + +: Implementaciones ligeras pero completas, por ejemplo implementar una base de datos en memoria a efectos de pruebas + + + +## Paquetes útiles + +### `flag` + +Este paquete nos permite leer parámetros pasados en la llamada al programa por linea de comandos. + +### `encoding/json` + +Este paquete nos permite salvar o recuperar estructuras de datos desde ficheros _json_ + +{{< admonition type=warning title="Referencias" open=false >}} +* [json con golang](https://golangdocs.com/json-with-golang) +* [read a json file](https://golangdocs.com/golang-read-json-file) +* [Parse json to a nested struct](https://penthaa.medium.com/golang-how-to-parse-json-data-into-a-nested-struct-29be89ce2ae8) +{{< /admonition >}} + + +**_marshalling_** + +: El proceso de codificar nuestra estructura de datos en _json_. + +**_unmarshalling_** + +: El proceso inverso al anterior para pasar de json a una estructura de datos + + +#### Arrays y Slices + +Podemos codificar en _json_ arrays o slices de _Go_ + +```go +package main + +import ( + "encoding/json" + "fmt" +) + +type Book struct { + Name string + Author string +} + +func main() { + + //-------------------------------------------------- + // Marshalling + book := Book{"C++ programming language", "Bjarne Stroutsrup"} + my_json, err := json.Marshal(book) + + if err != nil { + fmt.Println(err) + } + + fmt.Printf("Json for book is: %s\n", string(my_json)) // {"Name":"C++ programming language","Author":"Bjarne Stroutsrup"} + + //-------------------------------------------------- + // Unmarshalling + codString := `{"Name":"The Name of the Wind","Author":"Patrick Rothfuss"}` + + var cod Book + + err = json.Unmarshal([]byte(codString), &cod) + + if err != nil { + fmt.Println(err) + } + + fmt.Printf("Book for json is: %v\n", cod) + + //-------------------------------------------------- + // Arrays and Slices + // You can marshall/unmarshall to array or slice + + var books []Book + + booksJson := `[{"Name": "Hacking for Dummies", "Author": "Kevin Beaver"}, + {"Name": "Kerberos", "Author": "Jason Garman"}]` + + err = json.Unmarshal([]byte(booksJson), &books) + + if err != nil { + fmt.Println(err) + } + fmt.Printf("Slice for json is: %v\n", books) + + some_books := []Book{ + {Name: "Libro Uno", Author: "Author Uno"}, + {Name: "Libro Dos", Author: "Author Dos"}, + } + + some_books_json, err := json.Marshal(some_books) + + fmt.Printf("json for slice is: %v\n", string(some_books_json)) +} +``` + +#### Atributos "a medida" para _json_ + +Podemos añadir atributos _json_ a una `struct` que vamos a leer o a salvar con _json_. + +```go +package main + +import ( + "encoding/json" + "fmt" +) + +type Book struct { + Name string `json:"title"` // IMPORTANTE: Nada de espacios al definir los atributos + Author string `json:"artist"` +} + +func main() { + + //-------------------------------------------------- + // Marshalling + book := Book{"C++ programming language", "Bjarne Stroutsrup"} + my_json, err := json.Marshal(book) + + if err != nil { + fmt.Println(err) + } + + fmt.Printf("Json for book is: %s\n", string(my_json)) // {"title":"C++ programming language","artist":"Bjarne Stroutsrup"} +} +``` + +Además de cambiar los nombres de los campos podemos especificar un par de atributos `json` más: + +- `.omitempty`: nos permite saltarnos los datos si el campo está vacio +- `"-"` : nos permite saltarnos este campo por completo + +```go +type userdata struct { + login string `json:"username"` + real_name string `json:"name, .omitempty"` + password string `json:"-"` +} +``` + +#### _json_ y maps + +Siempre podemos cargar el _json_ en un `map`, especialmente si no están muy bien estructurados: + +```go +package main + +import ( + "fmt" + "encoding/json" +) + +func main() { + unstructuredJson := `{"os": {"Windows": "Windows OS","Mac": "OSX","Linux": "Ubuntu"},"compilers": "gcc"}` + + var result map[string]interface{} + + json.Unmarshal([]byte(unstructuredJson), &result) + + fmt.Println(result["os"]) // map[Linux:Ubuntu Mac:OSX Windows:Windows OS] +} +``` + +Por supuesto podemos también salvarlos en _json_: + +```go +package main + +import ( + "fmt" + "encoding/json" +) + +type Address struct { + Street string + City string +} + +type Person struct { + Name string + Address Address +} + +func main() { + p := Person{ + Name: "Sherlock Holmes", + Address: Address{ + "22/b Baker street", + "London", + }, + } + + str, err := json.Marshal(p) + + if err != nil { + fmt.Println(err) + } + + fmt.Println(string(str)) // {"Name":"Sherlock Holmes","Address":{"Street":"22/b Baker street","City":"London"}} +} +``` + +### `os/exec` + + + +Este paquete es parte de la biblioteca estándar de _Go_. Nos permite ejecutar comandos de sistema. + +Tres casos típicos: + +- Lanzar un comando y capturar su salida +- Lanzar un comando y comprobar el _exit code_ +- Lanzar comandos de larga duración + +```go +package main + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "strings" + "syscall" +) + +// printCommand prints command +func printCommand(cmd *exec.Cmd) { + fmt.Printf("==> Executing: %s\n", strings.Join(cmd.Args, " ")) +} + +// printError print human friendly error objects +func printError(err error) { + if err != nil { + os.Stderr.WriteString(fmt.Sprintf("==> Error: %s\n", err.Error())) + } +} + +// printOutput prints command output +func printOutput(outs []byte) { + if len(outs) > 0 { + fmt.Printf("==> Output: %s\n", string(outs)) + } +} + +// main el cuerpo ppal del programa +func main() { + // Create an *exec.Cmd + // Capture Stdout and Stderr together + cmd := exec.Command("echo", "Called from Go!") + printCommand(cmd) + output, err := cmd.CombinedOutput() // runs cmd and return combined output + printError(err) + printOutput(output) // => go version + + // Create an *exec.Cmd + cmd = exec.Command("go", "version") + + // Stdout buffer + cmdOutput := &bytes.Buffer{} + // Attach buffer to command + cmd.Stdout = cmdOutput + + // Execute command + printCommand(cmd) + err = cmd.Run() // will wait for command to return + printError(err) + // Only output the commands stdout + printOutput(cmdOutput.Bytes()) + + // create a command that will fail + cmd = exec.Command("ls", "/imaginary/dir") + var waitStatus syscall.WaitStatus + if err := cmd.Run(); err != nil { + printError(err) + // Did the command fail because of an unsuccessful exit code + if exitError, ok := err.(*exec.ExitError); ok { + waitStatus = exitError.Sys().(syscall.WaitStatus) + printOutput([]byte(fmt.Sprintf("%d", waitStatus.ExitStatus()))) + } + } else { + // Command was successful + waitStatus = cmd.ProcessState.Sys().(syscall.WaitStatus) + printOutput([]byte(fmt.Sprintf("%d", waitStatus.ExitStatus()))) + printOutput(cmdOutput.Bytes()) + } +} +``` + +Para el último caso _comandos de larga duración_, no queremos que nuestro programa espere al resultado del comando. Es mejor hacerlo de forma asíncrona. + +```go +package main + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "time" +) + +// printError print human friendly error objects +func printError(err error) { + if err != nil { + os.Stderr.WriteString(fmt.Sprintf("==> Error: %s\n", err.Error())) + } +} + +// printOutput prints command output +func printOutput(outs []byte) { + if len(outs) > 0 { + fmt.Printf("==> Output: %s\n", string(outs)) + } +} + +// main() programa ppal +func main() { + cmd := exec.Command("cat", "/dev/random") // creamos un comando + randomBytes := &bytes.Buffer{} // creamos un buffer + cmd.Stdout = randomBytes // asociamos la salida estándar al buffer + + err := cmd.Start() // Lanzamos el comando de forma asíncrona + printError(err) + + // Create a ticker that outputs elapsed time + ticker := time.NewTicker(time.Second) + go func(ticker *time.Ticker) { + now := time.Now() + for _ = range ticker.C { + printOutput( + []byte(fmt.Sprintf("%s", time.Since(now))), + ) + } + }(ticker) + + // Create a timer that will kill the process + timer := time.NewTimer(time.Second * 4) + go func(timer *time.Timer, ticker *time.Ticker, cmd *exec.Cmd) { + for _ = range timer.C { + err := cmd.Process.Signal(os.Kill) + printError(err) + ticker.Stop() + } + }(timer, ticker, cmd) + + // Only proceed once the process has finished + cmd.Wait() + printOutput( + []byte(fmt.Sprintf("%d bytes generated!", len(randomBytes.Bytes()))), + ) +} +``` + + + +### `tcell` + +Este paquete nos permite implementar interfaces de usuario basados en texto. + +Instalamos: + +```bash +go get -u github.com/gdamore/tcell +``` + +Recomiendan mirar primero el ejemplo incluido `mouse` + + +## Casos prácticos + +### Leer y escribir json + +Básico: + +```go +package main + +import ( + "encoding/json" + "fmt" +) + +// main ... +func main() { + x := map[string]string{ + "foo": "bar", + } + data, _ := json.Marshal(x) + fmt.Printf("Data contains:\n%s\n", (data)) +} +``` + +Result: + +```bash +Data contains: +{"foo":"bar"} +``` + +Un poco más complicado: + +```go +package main + +import ( + "encoding/json" + "fmt" +) + +// main ... +func main() { + + type person struct { + Name string `json:"name"` + Age int `json:"age"` + Description string `json:"descr,omitempty"` // DON'T use spaces in tags + secret string // Unexported fields are Unmarshaled + } + + x := person{ + Name: "Bob", + Age: 32, + secret: "Shhh!", + } + + data, _ := json.Marshal(x) + fmt.Printf("Data contains:\n%s\n", (data)) +} +``` + +Un ejemplo de *unmarshalling*, aunque siempre es preferible usar una `struc` si conocemos de antemano la estructura de los datos: + +```go +package main + +import ( + "encoding/json" + "fmt" +) + +// main test json +func main() { + data := []byte(`{"foo":"bar"}`) + + var x interface{} + _ = json.Unmarshal(data, &x) + fmt.Printf("x contains:\n%s\n", x) +} +``` + +Un ejemplo de : + +```go + +``` + + +## Best Practices + + + + + + +## Para estudiar + +- +- [Advanced Command Execution in Go](https://blog.kowalczyk.info/article/wOYk/advanced-command-execution-in-go-with-osexec.html) +- [Executing shell commands in Go](https://medium.com/rungo/executing-shell-commands-script-files-and-executables-in-go-894814f1c0f7) diff --git a/content/posts/notes_general/notes_guerrilla.md b/content/posts/notes_general/notes_guerrilla.md new file mode 100644 index 0000000..e967daf --- /dev/null +++ b/content/posts/notes_general/notes_guerrilla.md @@ -0,0 +1,94 @@ +--- +weight: 4 +title: "Apuntes de Autonomía Digital" +date: 2022-03-29T18:54:41+0200 +draft: true +summary: "Más valdría hablar de guerrilla digital" +categories: + - notes +tags: + - soberania digital +--- + + +{{< admonition type=warning title="Work in progress" open=true >}} + +No está completo. + +{{< /admonition >}} + +## ¿Qué es la Autonomía Digital? + +Últimamente se habla mucho de Soberanía Digital y de servicios auto alojados (o _self hosted_). Yo dejaría el término _Soberanía Digital_ para cuestiones que afectan a las naciones o a la Unión Europea en nuestro caso, por que los medios necesarios para alcanzar la Soberanía Digital tendrán que ser transnacionales para hacer frente a las potencias en juego, ya hablemos de paises como China, USA o Rusia, o a empresas como Google, Facebook, Amazon, Microsoft, etc. + +A nivel de individuo, creo que el término _Autonomía Digital_ sería más ajustado. + +Por otro lado, me temo que igual que la Soberanía Digital exige medios a nivel Europeo (como poco), la Autonomía Digital también exige esfuerzos que no están al alcance de todos. Cuanto más aprendo de este tema más convencido estoy que debería organizarse mediante la cooperación de varias personas, seguramente mediante una comunidad de intereses comunes, ya sea una asociación, una cooperativa o la forma que se le quiera dar. Creo que la Autonomía Digital es de interés para cualquiera, pero ciertamente no todos tendrán conocimientos suficientes para implementarla por su cuenta. Un ejemplo de comunidad organizada para procurarse la autonomía digital es [Disroot](https://disroot.org), no he buscado mucho así que es de suponer que habrá más. + +Añadir definición de autonomía digital + +## ¿Por qué? + +Detallar razones. Todo el mundo cita la privacidad como la primera razón para implementar estos servicios de modo auto-alojado. + +## ¿Cómo lo hacemos? + +Vamos a describir como implementaremos nuestro plan de autonomía. + +### Arquitectura: la infraestructura + +En mi caso tengo: + +**Dominio propio con su DNS Zone** + +: Contratado con un proveedor de internet reconocido. + + +- Correo electrónico subcontratado a un proveeder de buena reputación y que nos permita usar nuestro propio dominio. +- VPS +- Servicio de almacenamiento local en casa + +### Servicios deseados + +#### Almacenamiento + +Para que queremos + +## Implementación + +### Dominio propio + +### Correo electrónico + +### Almacenamiento + + + + + +## Referencias + +- +- +- No se que es esto +- [Veracrypt](https://veracrypt.fr/en/Downloads.html) +- [fscrypt](https://github.com/google/fscrypt) +- [ecryptfs](https://www.ecryptfs.org/) +- [gocryptfs](https://nuetzlich.net/gocryptfs/) +- [cryfs](https://www.cryfs.org/) +- + +### Servicios en la red + +- [disroot](https://disroot.org) +- [monocles](https://monocles.de/) + +## Cursos en la Domus + +### Primer curso el dia 10 de marzo de 2023 + +- Taller de iniciación a la seguridad digital. +- conceptos básicos de seguridad hash, password segura +- fortaleza de contraseñas +- gestor de contraseñas +- mas nociones generales diff --git a/content/posts/notes_general/notes_hugo.md b/content/posts/notes_general/notes_hugo.md new file mode 100644 index 0000000..d68dd9a --- /dev/null +++ b/content/posts/notes_general/notes_hugo.md @@ -0,0 +1,234 @@ +--- +weight: 4 +title: "Apuntes de Hugo" +date: 2020-04-17T11:52:19+02:00 +draft: false +summary: "Como usar Hugo para hacer un blog en gitlab" +categories: + - notes +tags: + - hugo + - git + - gitlab +--- +Como montar un blog como este, con Hugo + + + +Hace años que soy consciente de la existencia de las _Github Pages_ y/o las _Gitlab Pages_ Las probé en su momento en [Github](https://github.com/) pero no volví a prestarles atención por falta de tiempo. + +También se desde hace tiempo que hay generadores de sitios web estáticos, en su dia hice algunas pruebas con [Pelican](https://github.com/getpelican/pelican) (basado en Python) y con [Jekill](https://github.com/jekyll/jekyll) pero no llegué a utilizarlos para nada funcional. + +Como ahora tengo tiempo y sentía curiosidad por el tema, he investigado un poco como podría empezar rápido. + +## Hugo: el generador de sitios estáticos + +Un generador de sitios estáticos es un software que a partir de unos ficheros de contenido, y unos ficheros de temas genera un sitio estático completo. Es decir, un sitio web compuesto de htlm, css y nada más. + +Los ficheros de contenido suelen estar escritos en alguna variedad de markdown, org-mode, o algún lenguaje de marcas por el estilo. + +Los temas se suelen encontrar en grandes cantidades en la red, y escritos con distintos objetivos. Hay temas para hacer tu página de presentación personal y dar a conocer tu curriculum, temas para blogs, para galerias de fotos, para publicar documentación, etc. etc. + +El generador se encargará de procesar los ficheros de contenido para generar un sitio web completo con el estilo del tema que escojamos. + +Hay cantidad de generadores de sitios estáticos. Tras investigar un poco (muy poco la verdad) limité mis opciones a dos generadores: [Jekill](https://jekyllrb.com/) y [Hugo](https://gohugo.io/se). Tengo la sensación (totalmente subjetiva) de que Jekill es más potente e intuyo que será más complicado de usar así que he decidido empezar por Hugo. + +Hugo parece sencillo de instalar y de usar y no me ha dado pegas hasta ahora. + +Aquí tienes [una serie de videos](https://www.youtube.com/watch?v=qtIqKaDlqXo&list=PLLAZ4kZ9dFpOnyRlyS-liKL5ReHDcj4G3) cortitos donde se explican todos los detalles de Hugo si te interesan. + +### Instalación de Hugo + +Mi instalación es completamente espartana, me he descargado el paquete binario en su última versión (0.69.0 cuando escribo esto) desde [aquí](https://github.com/gohugoio/hugo/releases) Y he dejado el ejecutable `Hugo` en mi directorio `~/bin`. Nada más, ese directorio ya está en mi `PATH` así que con eso basta para mi. + +{{< admonition warning >}} +**Ojo** la primera vez instale el paquete binario _Hugo_ para Linux 64 bit, pero más tarde lo tuve que cambiar por el paquete **Hugo_extended** +{{< /admonition >}} + +Una vez instalado Hugo, no está de más hacer [el tutorial](https://gohugo.io/getting-started/quick-start/) + +## Temas para Hugo: Zdoc y Zzo + +Después de hacer el tutorial y algunas pruebas me di una vuelta por [la página de temas de Hugo](https://themes.gohugo.io/). Me interesaba buscar un tema que fuera adecuado para publicar guías algo extensas y que soportara publicar en varios idiomas. Al final escogí el tema [Zdoc](https://themes.gohugo.io/hugo-theme-zdoc/) por que cumplía mis requisitos, parecía muy bien documentado y con un aspecto gráfico que me gustaba. Pero en un momento dado entré en [la página del autor del tema](https://zzodocs.netlify.app) y casí sin darme cuenta me instalé el tema [zzo](https://github.com/zzossig/hugo-theme-zzo) que tampoco está nada mal, y parece más completo incluso que el anterior (con galerias de fotos, reseñas de libros, etc) así que continué con este. De todas formas, sigue pendiente probar el tema Zdoc para algúna guía larga y ver como queda. + +Hay que agradecerle a [Zzossig](https://github.com/zzossig) el currazo que se ha metido creando estos temas. + +### Creación de un site e instalación del tema + +Para usar un tema en nuestro site lo más práctico es hacer un fork del tema original. Por un lado podremos mandarle al autor nuestras contribuciones (en mi caso traducciones nada más) por si le interesa incorporarlas a la distribución oficial del tema. Y por otro lado, si hacemos otras modificaciones del tema las tendremos controladas en nuestro fork. + +Así que: + +1) Creamos un fork del tema original (en github en mi caso) + +2) Creamos un directorio en nuestro ordenador, para todos los ficheros de nuestro sitio estático, de aquí en adelante voy a suponer que nuestro sitio estático se va a llamar "comacero", puedes llamar al directorio como quieras pero yo le voy a llamar simplemente `blog`. + +```bash +mkdir blog +cd blog +``` + +3) Clonamos nuestro fork del tema elegido en el directorio `blog`, aquí haremos las modificaciones del tema (__ajusta la url a tu fork__) + +```bash +git clone https://github.com/zzossig/hugo-theme-zzo +``` + +4) Creamos el nuevo sitio localmente en nuestro ordenador con Hugo. Lo creamos dentro del directorio `blog`. Puedes llamarlo como quieras, yo lo voy a llamar igual que el sitio que queremos crear (`comacero` para este ejemplo): + +```bash +mkdir comacero +cd comacero +hugo new site . + +ls +archetypes config.toml content data layouts static themes +``` + +Ya tenemos el esqueleto de nuestro sitio estático, Hugo se ha encargado de poblarlo con directorios. + +Para usarlo en Gitlab nuestro sitio tiene que ser un repositorio git: + +```bash +git init +echo public > .gitignore +git add .gitignore +git commit -m "First commit" +``` + +{{< admonition info >}} +Cuando tengamos todo preparado y generemos nuestro sitio en local con Hugo, todo el sitio estático se creará en el directorio `comacero/public`. Pero __no queremos__ que ese directorio suba a Gitlab, es solo para nuestra "vista-previa" del site en local. Por eso lo añadimos al fichero `.gitignore` +{{< /admonition >}} + +Tenemos que añadir el tema, así que clonamos **nuestro fork** en la ruta `themes/zzo` + +{{< admonition warning >}} +Los _Gitlab Runners_ que son los procesos virtuales que van a generar nuestro sitio en Gitlab, __no pueden__ clonar un repo por ssh. Así que al añadir el tema tienes que hacerlo con un site `https://` o incluso clonar directamente el directorio. +{{< /admonition >}} + + +```bash +git submodule add https://github.com/zzossig/hugo-theme-zzo themes/zzo +git submodule update --remote --merge +``` + +Alternativamente **si tenemos nuestro site y nuestro fork del tema** en el mismo directorio `blog` podemos hacer referencia al submódulo con una ruta relativa: + +```bash +git submodule add ../hugo-theme-zzo themes/zzo +git submodule update --remote --merge +``` + +Pero si optamos por la segunda opción tenemos que ser cuidadosos al subir todo a Gitlab (lo explicamos luego) + +Una vez que tenemos nuestro tema descargado podemos copiar la configuración completa del site de ejemplo que viene con el tema. Desde el directorio donde tenemos nuestro site creado por Hugo, copiamos los ficheros de configuración y contenido del site de ejemplo en el site que hemos creado (que está vacío): + +```bash +rm config.toml +cp -r themes/zzo/exampleSite/* . +``` + +Y ya tenemos todo listo para probar, arrancamos el hugo como servidor web: + +```bash +hugo -D server +``` + +Si todo va bien tendríamos que poder conectarnos a `http://localhost:1313` y ver el site de ejemplo. + +#### Configuración del site + +Con el site funcionando podemos retocar los ficheros de configuración `config.toml` y `params.toml` que están en el directorio `config/_default`. Tendremos que adaptar la configuración de idiomas, nombre del sitio generado, etc. etc. en la página web de zzo tenemos instrucciones bien detalladas. + +## Integración con Gitlab Pages + +Como ya comenté, había mirado las páginas de Github y de Gitlab (y también las de Bitbucket) hace algunos años. Incluso había probado algún generador de sitios estáticos para temas del trabajo. Me quedé con ideas preconcebidas (y equivocadas): yo pensaba que toda la generación del sitio la hacías en local y después subias a Gitlab el sitio generado. Es decir, yo pensaba que después de ejecutar Hugo para generar todo el sitio estático en el directorio `public` subias ese contenido generado a Gitlab. + +No es así para nada. + +En Gitlab la generación de sitios estáticos está completamente integrada con el software Gitlab, lo que permite hacer [integración contínua](https://es.wikipedia.org/wiki/Integraci%C3%B3n_continua). Esta es otra asignatura que tengo pendiente así que me vino muy bien para ir aprendiendo. :smile: + +{{< admonition info >}} +Si quieres aprender como va el despliegue de sitios estáticos con _Gitlab Pages_ es muy recomendable que leas [la documentación de las Gitlab Pages](https://docs.gitlab.com/ee/user/project/pages/) y que para empezar [crees un site basado en la plantilla html/plain](https://docs.gitlab.com/ee/user/project/pages/getting_started/pages_bundled_template.html) (un template predefinido). +{{< /admonition >}} + +Los siguientes pasos serán: + +* Crear nuestro proyecto en Gitlab para subir nuestros ficheros del sitio +* Subir todo menos el contenido del directorio `public` +* Crear el fichero que le especifica al _runner_ que operaciones tiene que hacer para crear el sitio web + +### Crear nuestro proyecto en Gitlab + +En el disco duro yo tengo una estructura de directorios tal que así: + +{{< mermaid >}} +graph TD; + blog --> theme_zzo; + blog --> comacero; +{{< /mermaid >}} + +* En el directorio `comacero` tenemos nuestro site +* En el directorio `theme_zzo` tenemos el fork del tema original con nuestras modificaciones. + +En Gitlab he creado una estructura parecida: + +* Un grupo que se llama `comacero` +* Un proyecto `comacero.gitlab.io` dentro del grupo `comacero` que almacenará el sitio estático, ligado al directorio `blog/comacero` de mi disco duro +* Un proyecto `zzo_hugo_theme` que almacena mi fork del tema original, ligado la directorio `blog/theme_zzo` de mi disco duro + +{{< admonition info >}} +Conviene leerse la [documentación de Gitlab](https://docs.gitlab.com/ee/user/project/pages/getting_started_part_one.html#gitlab-pages-default-domain-names) con respecto a los nombres de dominio y su correspondencia con los nombres de proyecto. +{{< /admonition >}} + +### Subimos nuestro site a Gitlab + +* En el repo del directorio `blog/comacero` añadimos la url del remoto en Gitlab (`comacero.gitlab.io`) con `git remote set-url` +* En el repo del directorio `blog/theme_zzo` tenemos el fork del tema que apunta a un repo de github. Añadimos un remoto adicional apuntando a la url de Gitlab (`zzo_hugo_theme`) +* Hacemos un push desde nuestros repos locales hacia Gitlab y ya tenemos los repos preparados. + + +Para que nuestro repo genere las páginas web tenemos que incluir un fichero con las ordenes que va a ejecutar el _Gitlab runner_. + +### Activar el _runner_ de Gitlab + +Tenemos que crear en la raiz de nuestro repo `comacero.gitlab.io` un fichero `.gitlab-ci.yml` con el siguiente contenido: + +```.gitlab-ci.yml +# All available Hugo versions are listed here: https://gitlab.com/pages/hugo/container_registry +# image: registry.gitlab.com/pages/hugo:latest +image: registry.gitlab.com/pages/hugo/hugo_extended:latest + +variables: + GIT_SUBMODULE_STRATEGY: recursive + +test: + script: + - hugo + except: + - master + +pages: + script: + - hugo + artifacts: + paths: + - public + only: + - master +``` + +{{< admonition warning >}} +Mucho ojo, porque para nuestro tema necesitamos usar hugo-extended y no es fácil encontrar la imagen correcta en Gitlab. Afortunadamente había hecho pruebas con el template que nos da Gitlab para Hugo, y si os fijais en el comentario de la cabecera nos indica en que URL están todas las imágenes disponibles de Hugo. +{{< /admonition >}} + +## Final + +Siguiendo estos pasos deberíamos tener nuestro site disponible en la dirección https://comacero.gitlab.io, pero es muy posible que tarde **mucho** en estar disponible después de la creación. Yo tuve que esperar 48 horas antes de poder acceder al site. Aunque ahora las actualizaciones de contenido apenas llevan tiempo. Cuando subo un cambio el site se actualiza en cuestión de minutos. + + +## Problemas encontrados + +* Si escribes en _Pandoc_ (básicamente lo único que cambia es la extensión: `pdc` en lugar de `md`) **no funcionan** las tablas de contenido (TOC). En cambio se renderizan correctamente los párrafos al margen de los retornos insertados en el editor de texto. + +* Si escribes en _Markdown_ (ver el punto anterior) no puedes poner retornos de linea en el texto fuente (excepto para separar párrafos claro). Parece que Hugo los coserva al renderizar y el texto queda horrible con los retornos de linea insertados. diff --git a/content/posts/notes_general/notes_mapasTextiles.md b/content/posts/notes_general/notes_mapasTextiles.md new file mode 100644 index 0000000..ccd0538 --- /dev/null +++ b/content/posts/notes_general/notes_mapasTextiles.md @@ -0,0 +1,97 @@ +--- +weight: 4 +title: "Apuntes de Mapas Textiles" +date: 2023-03-29T17:26:48+0200 +draft: false +hiddenFromHomePage: true +summary: "Apuntes del curso de Mapas Textiles de Rosario Belda" +categories: +- notes +tags: +- textil +--- + +## Mapa de estrellas + +Desde el principio consideramos proyectos que nos dejaran abierta la posibilidad de incluir fuentes de luz. Así que pensamos en un mapa de los Faros de Galicia o un mapa estelar. Decidimos hacer un mapa de estrellas. + +## Fuentes de Mapas Celestes + +- [Mag-7 Star Atlas](https://www.siaris.net/astro/atlas/), disponible para descarga en [Internet archive](https://archive.org/details/Mag_7_Star_Atlas), un hermoso mapa a todo color en un tamaño muy manejable. (Siempre llevo una copia en el coche) Compilado por Andrew L. Jonhson. +- [The Beginners Star Atlas](https://vazhorov.wordpress.com/2020/09/03/beginners-star-atlas-v2/) Otro mapa compacto a todo color, obra de Ed Vazhorov. La página está en ruso, pero el artículo para descargar el mapa está en inglés. +- [The Night Sky Maps](https://www.olle-eriksson.com/night-sky-maps/information.html) PDF de descarga gratuita, un mapa genial (tres mapas en realidad) compilado por el astrónomo aficionado Olle Eriksson. +- [Deep Sky Hunter Atlas](https://www.deepskywatch.com/deep-sky-hunter-atlas.html) PDF de descarga gratuita. +- [Tri-Atlas](https://www.deepskywatch.com/deep-sky-hunter-atlas.html) El famoso mapa estelar Tri-Atlas de José R. Torres y Casey Skelton +- [Taki’s 8.5 Magnitude Star Atlas](https://allans-stuff.com/takis-8-5-magnitude-star-atlas/) Enlazo al mirror por que la página original de Toshimi Taki parece un poco abandonada. +- [Stellarium] y su [versión web](https://stellarium-web.org/) +- [Skymap Online](https://www.skymaponline.net/) +- [Mapa Interactivo de Sky&Telescope](https://skyandtelescope.org/interactive-sky-chart/) + + +## Etapas del proyecto + +### Mapa base + +Decidimos bajarnos el mapa desde el mapa interactivo de Sky&Telescope. Como queremos una proyección con Polaris en el centro del mapa, tenemos que fijar nuestra localización en algún punto del ecuador, es decir a 90 grados de latitud, la longitud nos da igual. + +Jugando con el tiempo podemos girar nuestro mapa. Aunque probablemente no influya en el resultado final hemos girado el mapa para que Orión nos quede a las tres en el hemisferio norte. Seguramente en el resultado final uniremos los dos hemisferios por la constelación de Orión (que nos gusta mucho). + +### Limpiar el mapa con Inkscape + +### Imprimir el mapa sobre tela + +### Bordando el mapa + + + + + +## Referencias generales del curso + +- [Mapas de palos de polinesia](https://www.vistaalmar.es/ciencia-tecnologia/historia/5565-los-mapas-de-palos-para-la-navegacion-en-las-islas-marshall.html) +- [Mapas de las novelas de Verne](http://verne.garmtdevries.nl/en/maps/originals.html) +- [Deformación en las proyecciones de una cabeza humana](https://recuerdosdepandora.com/geografia-2/una-cabeza-para-mostrar-como-los-mapas-distorsionan-la-realidad/) +- [Mapas Milhaud](https://mapasmilhaud.com/) +- [Recuerdos de Pandora](https://recuerdosdepandora.com/geografia-2/una-cabeza-para-mostrar-como-los-mapas-distorsionan-la-realidad/) el blog de Mapas Milhaud +- [Mola](https://youtu.be/sA_ats3TwT0) +- [Biblioteca del Congreso de EEUU](https://www.loc.gov/collections/world-digital-library/about-this-collection/) +- [Blog de Maxar Technologies](https://blog.maxar.com/) Una empresa de imágenes satelitales +- [ESRI Map Gallery](https://mapgallery.esri.com/) +- [ArcGIS](https://www.arcgis.com/home/webmap/viewer.html) +- [spaM de Madalena Parreira](https://madalenaparreira.com/SPAM) + +### Artistas + +- [Arounna Khounnoraj](https://www.instagram.com/reel/CkYWVApNW3X/?igshid=MDJmNzVkMjY=) +- [Agnes Hansella](https://agneshansella.com/id/article/oops-loops) +- Rieko Koga + - [Instagram](https://www.instagram.com/riekokoga/) + - [Entrevista en ArteMorbida](https://www.artemorbida.com/interview-with-rieko-koga/?lang=en) +- Paula Kovarik + - [Página personal](https://www.paulakovarik.com/) + - [Instagram](https://www.instagram.com/yellowbrickstudio/) +- Mirjam Gielen (Mirjam Textiles) + - [Instagram](https://www.instagram.com/mirjamtextiles/) + - [Facebook](https://www.facebook.com/mirjamtextiles/) +- Louise Watson + - [Página personal](https://louisemaywatson.blogspot.com/2019/03/threads-by-mirjam-gielen.html) + + +## Herramientas libres + +### cstich + +### colores + +#### online + +- [w3schools colour course](https://www.w3schools.com/colors/default.asp) Un curso muy completo de colores +- [htmlcolorcodes](https://htmlcolorcodes.com/color-picker/) Esta tiene bastante teoría del color, explica por encima los distintos tipos de armonía. +- [colr.org](https://www.colr.org/) Bastante pobre, es para hacer paletas a partir de imágenes. +- [Colorpicker](https://colorpicker.me/) Esta está bien, sólo hace lo justo, escoges un color y te sacas las armonías. +- [Colormind](http://colormind.io/) Se supone que usa Deeplearning para sacar las paletas de colores, no hace mucho más. + +#### herramientas + +- Gpick +- [delicolour](https://github.com/eepp/delicolour) diff --git a/content/posts/notes_general/notes_micros.md b/content/posts/notes_general/notes_micros.md new file mode 100644 index 0000000..63c4fe6 --- /dev/null +++ b/content/posts/notes_general/notes_micros.md @@ -0,0 +1,686 @@ +--- +weight: 4 +title: "Microprocesadores: apuntes" +date: 2023-09-08T12:12:46+0200 +draft: false +summary: "Apuntes de microprocesadores" +categories: + - notes +tags: + - ch340 + - attiny + - arduino + - platformio + - esp32 + - esp8266 + - wemos d1 + - stm32 + - STM32CubeIDE +--- + +{{< figure src="/images/microproc/arduinos_a.jpg" + title="arduinos" + width=300 >}} + + +## Notas conectando distintos procesadores al portátil y al IDE Arduino + +### Troubleshooting + +Prueba a parar el infame _ModemManager_: + +~~~bash +sudo systemctl stop ModemManager +~~~ + +Si tienes una versión antigua de _brltty_ (`brltty -V` menor que 6.5) puedes probar a parar el servicio: + +```bash +systemctl stop brltty-udev.service +``` + +### attiny + +Es necesario instalar `libusb-0.1-4` en nuestro S.O. + +~~~bash +sudo apt install libusb-0.1-4 +~~~ + +También debemos crear el fichero `/etc/udev/rules.d/49-digispark.rules` +con el siguiente contenido: + +~~~ +SUBSYSTEM=="usb", ATTR{idVendor}=="16d0", ATTR{idProduct}=="0753", MODE="0660", GROUP="dialout" +~~~ + +Una vez hecho esto lo attiny funcionan correctamente, pero son peculiares: hay que lanzar el download desde el arduino IDE con el dispositivo desconectado y conectarlo al puerto usb cuando nos lo indique el IDE. + +Configuración en IDE Arduino + +- **Board**: Digispark (Default - 16.5mhz) +- **Port**: No creo que importe +- **Programmer**: Micronucleous + +### ch340/ch341 + +#### Problemas con ModemManager + +Añadimos además una linea al fichero `99-arduino.rules` que queda así: + +~~~bash +# for arduino brand, stop ModemManager grabbing port +ATTRS{idVendor}=="2a03", ENV{ID_MM_DEVICE_IGNORE}="1" +# for sparkfun brand, stop ModemManager grabbing port +ATTRS{idVendor}=="1b4f", ENV{ID_MM_DEVICE_IGNORE}="1" +# for ardupilot brand, stop ModemManager grabbing port +ATTRS{idVendor}=="2341", ENV{ID_MM_DEVICE_IGNORE}="1" +# for ch401, stop ModemManager grabbing port +ATTRS{idVendor}=="1a86", ENV{ID_MM_DEVICE_IGNORE}="1" +~~~ + +Estas reglas como indican los comentarios evitan que _ModemManager_ capture estas marcas de dispositivos. + +#### Problemas con Brltty + +En Linux Mint Vera aparece un nuevo problema: El sistema usa una versión antigua (menor que 6.5) de _Brltty_ (se puede comprobar ejecutando `brltty -V`) En esta versión el _ch340_ es reconocido como un transductor Braille y capturado por el _Brltty_. Si no queremos tener problemas con los Arduino con _ch340_ tenemos que editar el fichero `/usr/lib/udev/rules.d/85-brltty.rules` y comentar la linea que se corresponde con el _Device ID_ del arduino: + +```bash +# Device: 1A86:7523 +# Baum [NLS eReader Zoomax (20 cells)] +# ENV{PRODUCT}=="1a86/7523/*", ENV{BRLTTY_BRAILLE_DRIVER}="bm", GOTO="brltty_usb_run" +``` + +Puedes recargar las reglas de dispositivos con el comando `sudo udevadm control --reload-rules` + +Otra forma más expeditiva de terminar con el problema es desactivar completamente y para siempre el servicio _brltty_. Yo prefiero dejarlo configurado. + +```bash +# Find-out the service +systemctl list-units | grep brltty + +# Mask the service to avoid starting it in the future +systemctl mask brltty-udev.service + +# Stop the service now +systemctl stop brltty-udev.service +``` + +### Arduino Nano con ch341 + +- **Board**: Arduino Nano +- **Processor**: Depende, hay que probar combinaciones de procesador y _bootloader_ +- **Port**: El que toque (ttyUSBn) +- **Programmer**: None + +### Arduino Pro mini + +Hay que usar FTDI adapter, con el mio el cable va plano. + +|Pin Adap. | Pin micro| +|--------- | ---------| +|3.3v | No usado | +|GND | GND | +|VBUS | VCC | +|TXD | RXI | +|RXD | TXO | +|DTR | DTR | + +- **Board**: Arduino Pro or Pro mini +- **Processor**: atmega328P 5V 16mhz +- **Port**: El que toque (ttyUSBn) +- **Programmer**: None + +![arduinos](/images/microproc/arduinos_a.jpg) + +### Arduino IDE + +#### Orígenes de bibliotecas configurados en el IDE Arduino + +~~~ +https://adafruit.github.io/arduino-board-index/package_adafruit_index.json +https://github.com/stm32duino/BoardManagerFiles/raw/master/STM32/package_stm_index.json +http://dan.drown.org/stm32duino/package_STM32duino_index.json +https://dl.espressif.com/dl/package_esp32_index.json +http://arduino.esp8266.com/stable/package_esp8266com_index.json +http://digistump.com/package_digistump_index.json +https://raw.githubusercontent.com/damellis/attiny/ide-1.6.x-boards-manager/package_damellis_attiny_index.json +~~~ + +### ESP8266 + +#### ESP8266 Arduino IDE + +Seguimos [esta +página](https://randomnerdtutorials.com/how-to-install-esp8266-board-arduino-ide/) +a ver a donde llegamos. + +Añadimos este origen en el IDE Arduino: + +Works!! al menos el blink + +La tarjeta que tenemos equipa un ESP-12E + +- **Board**: NodeMCU 1.0 (ESP-12E Module) +- **Port**: El que toque (ttyUSBn) + +#### Wemos D1 Lite + +##### Wemos D1 Lite en Arduino IDE + +- **Board**: LOLIN(Wemos) D1 mini Lite (con mi placa) +- **Port**: El que toque (ttyUSBn) +- Resto de parámetros por defecto + +Funciona sin necesidad de _virtualenv_ ni nada (cargado WifiScan) + +###### Probas + +Vamos a echar un ojo al código que nos propone Andreas Spiess en [este +video](https://www.youtube.com/watch?v=G73fiaOpUAc) + +Nos descargamos el repo de ejemplos de +[aqui](https://github.com/wemos/D1_mini_Examples) y los programas +corregidos por el señor Spiess de +[aqui](https://github.com/SensorsIot/Wemos-Shields) + +Aunque tampoco es que consigamos gran cosa mirando esto. + +Vamos a instalar la Biblioteca para el sensor SHT30 descargando de [el +github](https://github.com/wemos/WEMOS_SHT3x_Arduino_Library) + +Y con este programilla funciona: + +```cpp +#include + +SHT3X sht30(0x45); + +void setup() { + + Serial.begin(115200); + +} + +void loop() { + + if(sht30.get()==0){ + Serial.print("Temperature in Celsius : "); + Serial.println(sht30.cTemp); + Serial.print("Temperature in Fahrenheit : "); + Serial.println(sht30.fTemp); + Serial.print("Relative Humidity : "); + Serial.println(sht30.humidity); + Serial.println(); + } + else + { + Serial.println("Error!"); + } + delay(1000); + +} +``` + +Para probar el OLED instalamos la librería _Adafruit SSD 1306 Wemos +mini OLED_ (de Adafruit y mcauser) también necesitamos la _Adafruit GFX_ + +**NO FUNCIONA** (creo que me he cargado el OLED) + + +Biblioteca para MQTT en ESP8266 + +### ESP32 + + +#### ESP32 Arduino IDE + +Mi placa funciona en el IDE Arduino: + +* Arrancar el IDE desde el virtualenv de PlatformIO (el IDE Arduino necesita pyserial instalado) +* **Board**: ESP32 Dev Module +* El resto por defecto + +Probar con esto: +- +- Una guía: + +#### TTGO T4 + +* Instalamos la librería *TFT_eSPI* + +Aparentemente hay que usar el 'ESP32 Dev Module' en el IDE Arduino + +* [Una demo con cryptomonedas](https://github.com/LilyGO/TTGO-T4-DEMO) +* [Test code](https://github.com/Xinyuan-LilyGO/LilyGo_Txx) +* [Hackaday](https://hackaday.com/2018/05/23/esp32-boards-with-displays-an-overview/) +* [Proyectos con Arduino y MicroPython](https://kreier.github.io/t-display/) + + +Andreas Spies + +* [Hoja comparativa](https://docs.google.com/spreadsheets/d/1Mu-bNwpnkiNUiM7f2dx8-gPnIAFMibsC2hMlWhIHbPQ/edit#gid=0) +* [Video comparativo](https://www.youtube.com/watch?v=s12XuR7BJkw) + +Mas cosas: + +* [esta puede ser útil o no](https://github.com/LilyGO/TTGO-T2-SSD1331-SD) +* [Y esta igual](https://sites.google.com/site/jmaathuis/arduino/lilygo-ttgo-t-display-esp32) + +#### ESP32-C3 + +Para el Arduino IDE instalamos las bibliotecas _latest_ siguiendo las instrucciones de [este enlace](https://docs.espressif.com/projects/arduino-esp32/en/latest/installing.html#windows-manual-installation) + +Basicamente: + +```bash +mkdir -p ~/Arduino/hardware/espressif && \ +cd ~/Arduino/hardware/espressif && \ +git clone https://github.com/espressif/arduino-esp32.git esp32 && \ +cd esp32/tools && \ +python3 get.py +``` + +Reiniciamos el _Arduino IDE_ si estaba arrancado y en el menú de _Boards_ veremos la entrada _ESP32 Arduino (in sketchbook)_ con las últimas versiones de las tarjetas. + +{{< admonition type=warning title="Actualizar ESP32 Arduino (in sketchbook)" open=true >}} + +Es de suponer que estas bibliotecas se actualizan via git, + +{{< /admonition >}} + + + +### STM32 Blue Pill + +Los micros que yo tengo son STM32F103C8T6 + +Para programar el _Blue Pill_ desde el _Arduino IDE_ tenemos dos posibles ruta para añadir en nuestras _Arduino Ide Preferences_ + +- La de la comunidad stm32duino (ver [el github](https://github.com/rogerclarkmelbourne/Arduino_STM32)): `http://dan.drown.org/stm32duino/package_STM32duino_index.json` esta versión soporta las placas Mapple. Con este origen en el _Board Manager_ de Arduino nos aparecen dos opciones: + - __STM32F1xx/GD32F1xx boards by stm32duino__ + - __STM32F4xx boards by stm32duino__ +- La que parece oficial de STMicroelectronics:`https://github.com/stm32duino/BoardManagerFiles/raw/main/package_stmicroelectronics_index.json`. Puede comprobarse la dirección [aquí](https://github.com/stm32duino/Arduino_Core_STM32). Con este origen en el _Board Manager_ del IDE nos aparece una opción: + - __STM32 MCU based boards by STMicroelectronics__ con soporte para muchísimas placas + + + +## PlatformIO ## + +Hacemos la instalación de _Platformio_ a través de _VS Code_ como detallamos [en este documento](https://git.comacero.com/salvari/guide_LinuxMint#platformio). + +{{< admonition type=tip title="udev rules" open=true >}} + +En mi ordenador todas las pruebas con distintos micros han funcionado hasta ahora sin problemas con los _udev rules_ que tenía configurados. Pero desde el propio _Platformio_ recomiendan instalar a mayores el fichero [99-platformio-udev.rules](https://github.com/platformio/platformio-core/blob/develop/scripts/99-platformio-udev.rules) + +Para recargar las _udev rules_: `udevadm control --reload-rules && udevadm trigger` + +{{< /admonition >}} + +Una vez que _VS Code_ instala el _Platformio_ tendremos disponible un entorno virtual con las herramientas que se han instalado en `~/.platformio/penv/`. Yo me he creado un alias `mypio` que me permite activar ese entorno virtual, por ejemplo para acceder al _Platformio CLI_. + + +_Platformio_, por defecto, deja los proyectos en `~/Documents/PlatformIO/Projects` + +Probado con un Arduino Nano y funciona a la primera con VsCode: + +```ini +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:nanoatmega168] +platform = atmelavr +board = nanoatmega168 +framework = arduino +``` + +### ESP32 + +#### Framework 'arduino' + +Creamos un nuevo proyecto con + +```ini +[env:esp32dev] +platform = espressif32 +board = esp32dev +framework = arduino +``` + +Si es la primera vez que usas el _framework_ (_arduino_ en este caso) tardará un ratillo en descargarlo de internet. + +#### Framework 'ESP-IDF' + +Creamos un nuevo proyecto con: + +```ini +[env:esp32dev] +platform = espressif32 +board = esp32dev +framework = espidf +``` + +El código del blink: + +```cpp +#include +#include +#include + +#define LED_PIN 2 + +void led_blink(void *pvParams) { + gpio_pad_select_gpio(LED_PIN); + gpio_set_direction (LED_PIN,GPIO_MODE_OUTPUT); + while (1) { + gpio_set_level(LED_PIN,0); + vTaskDelay(1000/portTICK_RATE_MS); + gpio_set_level(LED_PIN,1); + vTaskDelay(1000/portTICK_RATE_MS); + } +} + +void app_main() { + xTaskCreate(&led_blink,"LED_BLINK",512,NULL,5,NULL); +} +``` + +{{< admonition type=note title="TODOs" open=false >}} + +- [Probar Wemos D1](https://docs.platformio.org/en/latest/boards/espressif8266/d1_mini.html) +- [Probar con ESP32](https://docs.platformio.org/en/latest/tutorials/index.html) +- [Probar ESP32-S3](https://docs.platformio.org/en/latest/boards/espressif32/esp32-s3-devkitc-1.html#board-espressif32-esp32-s3-devkitc-1) +- [ESP32 Wrover](https://docs.platformio.org/en/latest/boards/espressif32/esp-wrover-kit.html#board-espressif32-esp-wrover-kit) +- [Wemos D1 and Micropython](https://www.wemos.cc/en/latest/tutorials/index.html) + +{{< /admonition >}} + + + +### Referencias ## + +* [PlatformIO samples (github)](https://github.com/platformio/platformio-examples) +* [PlatformIO for Arduino, ESP8266, and ESP32 Tutorial (Andreas)](https://www.youtube.com/watch?v=0poh_2rBq7E) +* [PlatformIO and Wemos D1](https://www.youtube.com/watch?v=V6bG-UvD54Q) + + +## Micropython + +{{< admonition type=info title="Referencias Micropython" open=true >}} +- [micropython.org](https://micropython.org/) +- [Micropython on ESP32](https://bhave.sh/micropython-urequests/) +- [Run Micropython on Docker Container](https://bhave.sh/micropython-docker/) +{{< /admonition >}} + +### Instalación del editor Thonny + +Para poder usar la biblioteca Tkinter en cualquier entorno virtual parece que tenemos que tener instalado el paquete `python3-tk` en nuestro sistema. + +En principio nos dicen que Thonny viene con Python 3.7 incluido, pero para Linux no parece que traiga ningún Python. Thonny arrancará usando el Python por defecto del entorno virtual que creemos, pero nos dejará seleccionar otras versiones si las ve disponibles. De momento creamos el entorno virtual para Thonny con la última versión disponible (en _pyenv_) de Python 3.7 + + +```bash +sudo apt install python3-tk # Asegurate de tener esto instalado + +pyenv install 3.7.13 # Instalamos el último python 3.7 +pyenv virtualenv 3.7.13 ve_thonny # creamos el entorno virtual + +mkdir ~/work/thonny # creamos un directorio de trabajo +cd ~/work/thonny +pyenv local ve_thony # asignamos un ve al directorio +pip install thonny # instalamos thonny +thonny # y lo arrancamos +``` + +### MicroPython con las Raspberry Pi Pico + +Para instalar el MicroPython en nuestra Pico necesitamos que se monte como un dispositivo de almacenamiento externo. Si viene directa de fábrica es el comportamiento por defecto, de lo contrario tenemos que: + +* Desconectar el USB de la Pico del ordenador +* Con el USB desconectado pulsamos el boton de _Boot Select_ en la Pico y sin soltarlo la conectamos por USB al ordenador +* Mantenemos el botón pulsado tres segundos y soltamos, deberíamos ver la Pico montada como almacenamiento externo + +Ahora basta con descargar el fichero UF2 de MicroPython desde [la página oficial](https://www.raspberrypi.com/documentation/microcontrollers/micropython.html) y dejar una copia en el sistema de ficheros de la Pico, en cuanto tenga el fichero descargado se reiniciará y ejecutará el MicroPython + +Una vez tengamos la Pico conectada y ejecutando el Python podemos usar el editor _Thonny_ para conectarnos a ella, basta con arrancar el edito y en la opcion `Run::Select Interpreter` escoger la opción `MicroPython (Raspberry Pi Pico)`, con eso ya estaremo conectados al interprete de la Pico y podremos ejecutar programas (Hay que salvarlos en la Pico, el editor te dejará escoger donde quieres salvarlos) + +#### T-Pico C3 + +- [Github site from Xinyuan Lilygo](https://github.com/Xinyuan-LilyGO/T-PicoC3) +- [Report on CNX](https://www.cnx-software.com/2022/05/02/lilygo-t-picoc3-board-merges-rp2040-esp32-c3-integrates-color-display/ ) + + +## STM32 y el entorno STM32CubeIDE + +{{< admonition type=info title="Referencias" open=true >}} + +- [STM32F1xxxx Reference Manual (pdf)](https://www.st.com/content/ccc/resource/technical/document/reference_manual/59/b9/ba/7f/11/af/43/d5/CD00171190.pdf/files/CD00171190.pdf/jcr:content/translations/en.CD00171190.pdf) +- [STM32F4xxxx Reference Manual (pdf)](https://www.st.com/resource/en/reference_manual/rm0090-stm32f405415-stm32f407417-stm32f427437-and-stm32f429439-advanced-armbased-32bit-mcus-stmicroelectronics.pdf) +- [USB intro en la wiki de st.com](https://wiki.st.com/stm32mcu/wiki/Introduction_to_USB_with_STM32) +- [Una página con algunas referencias a distintas placas con micros STM32](https://stm32-base.org/) + +{{< /admonition >}} + + +De momento solo estoy probando la BluePill con STLink V2 (chino). Las BluePill que tengo yo llevan el micro F103C8T6 + +Instalamos el STM32CubeIDE con la opción de paquetes `.deb` + +{{< admonition type=tip title="Programar con STLink V2" open=true >}} + +Para cualquier proyecto de STM32 donde queramos programar el micro +usando el _ST Link V2_, siempre tenemos que configurar el _debugger_ +desde la opción `System Core::sys::` tenemos que asegurarnos de que: +- `Debug: Serial Wire` +- `Timebase Source: SysTick` + +{{< /admonition >}} + +### Programando un Blink + +Configuramos el pin 13 (corresponde al pin de usuario) como `output` no le ponemos nada a mayores. Si optamos por ponerle una etiqueta, p. ej. `LEDUSER` podemos hacer referencia a la etiqueta en el código. + +{{< admonition type=warning title="Bloques de código" open=true >}} + +Es **IMPORTANTE** insertar nuestro código en los bloques señalados por comentarios, por que de lo contrario, si cambiamos algo en la configuración del micro y regeneramos el código lo que no esté en los bloques marcados se eliminará automáticamente. + +{{< /admonition >}} + + +```c +/* USER CODE BEGIN WHILE */ +while (1) +{ + HAL_Delay(1000); + HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, 0); + HAL_Delay(1000); + // Otra solución + HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); + HAL_Delay(1000); + // Usando etiquetas de GPIO + HAL_GPIO_TogglePin(LEDUSER_GPIO_Port, LEDUSER_Pin); + HAL_Delay(1000); + + /* USER CODE END WHILE */ + /* USER CODE BEGIN 3 */ +} +``` + +### STM32 USB-CDC + + +**¡Ojo!** además de configurar correctamente el USB, es decir, activarlo en la sección de _Connectivity_ y configurar correctamente el reloj para poder dar señal al USB, tendremos que añadir el _Middleware_ que nos interese (en este ejemplo usamos _Virtual Port COM_ ) + +También tendremos que hacer un `#include "usbd_cdc_if.h"` en la sección correspondiente a los _Private Includes_ de nuestro `main.c`. + +Sección de _includes_: + +```c +/* Private includes ----------------------------------------------------------*/ +/* USER CODE BEGIN Includes */ +#include "usbd_cdc_if.h" +/* USER CODE END Includes */ +``` + +Sección del programa: + +```c + /* Infinite loop */ + /* USER CODE BEGIN WHILE */ + uint8_t txBuffer[] = {'H', 'o', 'l', 'a', '\n'}; + while (1) + { + CDC_Transmit_FS(txBuffer, 5); + HAL_Delay(100); + + /* USER CODE END WHILE */ + + /* USER CODE BEGIN 3 */ + } + /* USER CODE END 3 */ +``` + +Si cargamos el programa y conectamos la _Blue Pill_ al usb de nuestro ordenador (yo desconecto previamente el stlink) podremos comprobar que estamos recibiendo con: + +```bash +cat /dev/ttyACM0 +``` + + + + + + + +## PlatformIO + +### Instalando el Espressif SDK + +- Abrimos VS code +- Abrimos el Platformio +- Nuevo proyecto +- Escogemos **Espressif ESP32 Dev** como _board_ +- Escogemos **ESP-IDF** como _framework_ + +Con esto conseguiremos que el módulo _Platformio_ descargue el _framework_ de Espressif en el directorio `~/.platformio/packages/framework-espidf` + + + +## Experimentos con el ESP32 + + + + +## Conversores usb ttl ## + + + +### CP2102 5PIN ### + +1. tamaño de PCB: 26,5mm * 15,6mm +2. PCB Color: rojo +3. Peso: 4g +4. Uso del nuevo chip CP2102 +5. Con fusible reajustable. En el caso de cortocircuito accidental + puede proteger eficazmente su dispositivo y el puerto USB del + ordenador para descargar +6. Con tres leds: Indicador de potencia, recepción de datos del + indicador, indicador de transmisión de datos, estado del trabajo de + un vistazo +7. 3,3 V y 5V con dos salidas de potencia +8. Todos los pines de plomo útiles +9. Con la salida de señal de reinicio puede ser directamente Pro mini + y otra tarjeta Arduino para descargar! +10. Sistemas operativos compatibles: + + - Windows 98 / Me / 2000 / XP / 7 + - MAC OS-9 + - MAC OS + - X-Windows CE + - Linux 2,40 o posterior + +### PL2303HX ### + +Características: + +* Controlador importado RS232 TTL, que puede estabilizar el flash con alta velocidad +* Fusible de autorecuperación de 500mA para protección +* Dos indicadores de transmisión de datos pueden monitorizar el estado de la transferencia de datos en tiempo real +* Reserve interfaz de pin de 3,3 V y 5V, fácil para el DDWRT de diferentes sistemas de voltaje que necesitan energía +* La placa entera está recubierta por una manga termoretráctil transparente de alta calidad, haciendo que la PCB esté aislada desde el exterior, +* Para que la tabla no se queme con Un corte corto de material. +* Paquete electrostático, asegura que la placa no se dañará antes de su uso +* Apoyo WIN7 sistema + +Especificaciones: + +* Dimensión: about50x15x7mm +* Peso neto: 5g +* Peso del paquete: 15g +* Color: azul el pin con muchísimas patitas + +### USB a TTL CH340G ### + + +Características: + +* USB incorporado al chip de transferencia TTL. +* Diseñado para ser utilizado para proyectos electrónicos USB a TTL. +* Salida de interfaz TTL, fácil de conectar a su MCU. +* LED de estado +* Salida de potencia Dual de 3,3 V y 5V, funciona con dispositivos de 3,3v y 5v. +* Tamaño: 55mm * 16mm +* ¿Servidor 2008/Win7/Win8 32 bits / 64 bits? + +1. soporte de WINDOWS 98/ME/2000/XP/Server 2003/VISTA/ +2. 3 V3 y 5V por una selección de riesgo de cortocircuito; +3. No solo tiene led de alimentación PWR, también hay indicador TXD y + RXD, si el producto es fácil de aprender e intuitivo de trabajar en + la ausencia delinstrumento en la caja; +4. pin amarillo de alta calidad, pin duradero que el mercado negro, hermoso; +5. Nuevos chips CH340G originales, los chips se recomiendan USB + oficial a los chips TTL no aparecerán debido a diferentes + controladores/diferentes ordenadores como resultado de + incompatibilidades. + + + +CH340/módulo interruptor USB TTL/Transferencia USB RS232/sobre PL2303/ downloader/ 9 cepillo Junta STC oficial descargar chip Perfectamente compatible con todas las Series MCU no será debido a los diferentes controladores que conduzcan a problemas como PL2303 no puede descargar el programa. Ultra-estable + + + +## Referencias ## + +### Cursos: + +* esp-32: + - [idf-esp framework course](https://learnesp32.com/) 17 horas y media de video. El curso tiene muy buena pinta pero vale 50 eurazos. + - [ESP32 and PlatformIO IoT Project](https://www.udemy.com/course/esp32-platformio/) By Dalmaris parece muy orientado a la nube y con Platformio, son 10 horas de video + - [MicroPython with the ESP32](https://www.udemy.com/course/micropython-with-the-esp32/) By Dalmaris 10 horas y media de videos acerca de Micropython en esp32. + - [Deepbluembedded](https://deepbluembedded.com/esp32-programming-tutorials/) Una colección de cursos que parecen basados en el Arduino IDE + - [IoT Application Development with the ESP32 Using the ESP-IDF](https://www.udemy.com/course/iot-application-development-with-the-esp32-using-the-esp-idf/) by Kevin Aguilar (ocho horas de video) Un curso avanzado de uso del ESP-IDF y FreeRTOS, pero parece que depende/usa el AWS de Amazon. + - [ESP32 for Arduino Makers](https://www.udemy.com/course/esp32-for-arduino-makers/) Curso con Arduino IDE by Dalmaris, tal vez no sea tan malo + +* stm-32 + - [De Arduino a STM32 (by Tutoelectro)](https://www.youtube.com/watch?v=E8JpLtMlokw&list=PL1Hs_F1k2mdTwlGSv7lglkF_LtY0125OB) + + +### Referencias Generales ### + +* [Smart health connected devices](https://www.absmarthealth.com/how-to-diy-smart-health-connected-objects/) +* [Zephir project RTOS by The Linux Foundation](https://docs.zephyrproject.org) diff --git a/content/posts/notes_general/notes_odoo.md b/content/posts/notes_general/notes_odoo.md new file mode 100644 index 0000000..1373727 --- /dev/null +++ b/content/posts/notes_general/notes_odoo.md @@ -0,0 +1,88 @@ +--- +weight: 4 +title: "Apuntes de Odoo" +date: 2022-04-19T11:35:38+0200 +draft: false +summary: "Notas de Odoo un CRM de software libre" +categories: +- notes +tags: +- crm +- odoo +- python +--- + + +{{< admonition type=danger title="Work in Progress" open=true >}} + +Estos apuntes **NO ESTÁN ACABADOS** son sólo para uso personal. + +{{< /admonition >}} + +## ¿Qué es Odoo? + +## Instalación de Odoo 15.0 en Linux Mint Una + +Básicamente he seguido las indicaciones de la documentación oficial de la version 15.0 que podemos ver [aquí](https://www.odoo.com/documentation/15.0/administration/install/install.html#id7). + +- Yo he optado por hacer un fork en mi github y clonar desde mi fork +- Además he creado un _virtualenv_ con pyenv para ejecutar Odoo + +```bash +mkdir odooTest +pyenv virtualenv 3.9.6 ve_odooTest +pyenv local ve_odooTest +my_ve # Esto instala mis bibliotecas básicas de python y el pylsp +git clone git@github.com:salvari/odoo.git +``` + +Debemos comprobar que tenemos instaladas las dependencias básicas de nuestro SO: + +```bash +sudo apt install python3-dev libxml2-dev libxslt1-dev libldap2-dev libsasl2-dev \ + libtiff5-dev libjpeg8-dev libopenjp2-7-dev zlib1g-dev libfreetype6-dev \ + liblcms2-dev libwebp-dev libharfbuzz-dev libfribidi-dev libxcb1-dev libpq-dev +``` + +Y dentro de nuestro _virtualenv_ instalar las dependencias Python de Odoo: + +```bash +cd odooTest/odoo +pyenv which pip # para asegurarnos de donde estamos +pip install -r ./requirements.txt +``` + +Yo tengo ya instalado en mi sistema _wkhtmltopdf_ + +Necesitamos la base de datos _Postgress_, yo la voy a instalar aislada en un contenedor Docker, así que en nuestro linux instalamos sólo el cliente: + +```bash +sudo apt install postgresql-client +docker pull postgres +docker volume create postgres-data +docker run -d --name postgres-server -p 5432:5432 -v postgres-data:/var/lib/postgresql/data -e "POSTGRES_PASSWORD=la_password" postgres +``` + +Una vez instalada con nuestro cliente podemos conectarnos y crear el usuario para Odoo: + +```bash +psql -h 127.0.0.1 -U postgres +create database odoodb; +create user odoo with encrypted password 'odoopass'; +grant all privileges on database odoodb to odoo; +``` + + +Ya tenemos ***Odoo*** instalado y la base de datos con su usuario dedicado preparada en nuestro _Postgresql_ así que podemos arrancar el servidor (asegúrate de tener el _virtualenv_ activo): + +```bash +/odoo-bin --addons=./addons --db_host=127.0.0.1 --db_user=odoo --database=odoodb --db_password=odooPass +``` + + +El usuario por defecto para hacer login es `admin` con password `admin`. Deberíamos crear otro usuario administrador cuanto antes y borrar el que viene por defecto. + + +## Referencias + +- Odoo Developer Tutorials: [Getting Started](https://www.odoo.com/documentation/15.0/developer/howtos/rdtraining/01_architecture.html) diff --git a/content/posts/notes_general/notes_orgmode.md b/content/posts/notes_general/notes_orgmode.md new file mode 100644 index 0000000..3a43242 --- /dev/null +++ b/content/posts/notes_general/notes_orgmode.md @@ -0,0 +1,300 @@ +--- +weight: 4 +title: "Apuntes: org-mode en Emacs" +date: 2020-12-17T10:19:04+0100 +draft: false +summary: "Aprendiendo a usar org-mode" +categories: +- notes +tags: +- emacs +- org-mode +--- + +Apuntes **no terminados** sobre Emacs y org-mode + + + +{{< admonition type=warning title="Work in progress" open=true >}} +Estos apuntes no están completos, (ni de lejos) +{{< /admonition >}} + + +_org-mode_ es un invento de [Carsten Dominik](https://staff.fnwi.uva.nl/c.dominik/). Mr. Carsten lo inventó como un método para implementar GTD, pero poco a poco lo fue refinando añdiendo _Emacs Calc_ y _org-babel_. A partir de ahí no ha parado de mejorar. + +## Esquema básico con cabeceras + +Lo primero que te encuentras en _org-mode_ es un esquema basado en cabeceras: + +* Cada cabecera empieza con uno (o varios) caracteres * +* Pulsando `Tab` en la linea de cabecera se colapsa o expande esa + sección +* Pulsando `S-Tab` en cualquier parte del documento se cambia el + estado colapsado/expandido en todo el documento +* Pulsando `Alt` (que llamamos `Meta` en Emacs) y las flechas derecha + e izquierda en una linea de cabecera cambiamos el nivel de la + cabecera +* Pulsado `M-Up` o `M-Down` en una linea de cabecera la cambiamos de + posición en el documento +* La combinación `C-c C-w` en una cabecera nos permite cambiarla de + rama (interactivo) + + +## Listas de Tareas + +Cualquier cabecera puede marcarse como `TODO` o `DONE` (son las +opciones por defecto), basta con presionar `S-right` o `S-left` en la +linea de cabecera para ir cambiando de estado. Alternativamente puedes +usar `C-c C-t` para poner un estado interactivamente (más rápido) + +Si necesitamos algo más sofisticado podemos añadir estados en la +cabecera de nuestro fichero `.org` + +Los estados a la izquierda del _pipe_ son `TODO`, y a la derecha son +`DONE` + +```org-mode +#+SEQ_TODO: Next(n) TODO(t) Waiting(w) Someday(s) | DONE(d) CANCELLED(c) +``` + +Si cambias las lineas de configuración en la cabecera, puedes recargar +el `local setup` con `C-c C-c` (en la cabecera) + +### Agendar tareas + +Para agendar (_schedule_) una tarea usamos `C-c C-s`, en la linea de +_schedule_ podemos añadir una hora o un periódo: + +```org-mode +SCHEDULED: <2020-10-15 Thu 16:00> +SCHEDULED: <2020-10-15 Thu 16:00-17:00> +``` + +En `org-mode` normalmente se considera que la fecha `SCHEDULED` es el momento en que se debe **empezar** con la tarea. + +Si queremos definir la fecha en la que la tarea debe estar **realizada** usamos `DEADLINE` que se define de forma análoga a `SCHEDULED` con la combinación `C-c C-d` + +En generál en `org-mode` las fechas entre ángulos son fechas _activas_ y las fechas entre corchetes son _informativas_, una vista de tarea podría ser: + +```org-mode +** TODO [2021-03-15 Mon] Probar Caddy :hometask: +SCHEDULED: <2021-03-20 Sat> DEADLINE: <2021-03-21 Sun> + :PROPERTIES: + :Effort: 2:00 + :Beat: Now + :END: +``` + +Donde tenemos una fecha de captura de la tarea (informativa) y las fechas de `SCHEDULED` y `DEADLINE` (activas). + + +### Vista de agenda + +Nuestro fichero o ficheros `.org` pueden tener distintas tareas con fechas asignadas pero no estarán ordenados cronológicamente. + +Para una visión cronológica disponemos de las _Agenda Views_. + +Para acceder a las vistas de agenda usamos: `C-c a` y después seleccionamos la vista deseada. **Recuerda** si usamos un prefijo `C-u n` justo antes de `C-c a` limitaremos a `n` dias las vistas de agenda. + +Dentro de la vista agenda, podemos activar el _Follow mode_ con `S-f` para que la vista del fichero se mueva a la tarea señalada en la +agenda. También podemos usar las teclas `f` y `b` para movernos adelante,atrás (p.ej. en la agenda semanal cambiaríamos a semana anterior +o siguiente) + +Por defecto el margen de aviso para tareas con `DEADLINE` es de 14 dias, pero se puede configurar en la variable `org-deadline-warning-days` + +### Agenda Advance +- `C-c a` Agenda +- Se puede limitar a n dias con prefijo: `C-u n` P.ej. para ver las task para hoy pulsamos `C-u 1 C-c a` +- Si seleccionamos `a` entramos en la agenda, con el prefijo podemos limitar a n dias +- Con `t` vamos a la lista de tareas podemos filtrar más siguiendo instrucciones +- Con `T` vamos a la lista de tareas con una o varias palabras clave especiales (p.ej. `TODO|NEXT`) +- Con `m` podemos seleccionar un TAG y más criterios. P. ej. + `phone+TODO="NEXT"`. Con la mayúscula `M` restringimos a tareas + `TODO` +- Con `s` podemos buscar una palabra en todas las tareas (buscará en + cabecera y contenido de la tarea). Si usámos la mayúscula `S` + restringimos a las tareas `TODO` + + Ejemplos + - `+mini +docker` todas las tareas que contengan las dos palabras: `mini` y `docker` + - `{regexp}` las regexp van entre llaves + +### Vistas de agenda "a medida" + +```elisp +(setq org-agenda-custom-commands + '(("c" "Desk Work" tags-todo "computer" ;; (1) (2) (3) (4) + ((org-agenda-files '("~/org/widgets.org" "~/org/clients.org")) ;; (5) + (org-agenda-sorting-strategy '(priority-up effort-down))) ;; (5) cont. + ("~/computer.html")) ;; (6) + ;; ...other commands here + )) +``` + +En la primera linea tenemos el atajo (1), el título de la búsqueda +(2), el criterio de búsqueda, `tags-todo` (3) y la cadena que queremos +buscar `"computer"` + +En la segunda linea (5) podemos poner restricciones y/o condiciones a +nuestra búsqueda, en este caso restringimos los ficheros a buscar y en +que orden mostramos los resultados. + +En la sexta linea especificamos que esta busqueda se exportaria al +fichero `~/computer.html` en el caso de que hiciésemos una exportación +de la agenda. + + + +### Tareas repetitivas + +Podemos hacer las tareas repetitivas añadiendo la especificación de +repetición a la fecha _schedule_: + +- `+1w` Repetir en una semana (w, d, m, y) +- `++1w` Siguiente repetición en una semana, pero asegurate que sea en + el futuro. Esto nos garantiza que la repetición se programa bien en + el caso de que completemos la tarea con retraso +- `.+1w` Una semana después de marcarla como realizada + + +### Checklist + +- `- [ ]` como esta +- `M-S-enter` genera una nueva entrada en la checklist +- `C-c C-c` cambia el estado de la linea +- Con [/] o [%] podemos ver el estado del avance de la lista de checklists + +## Tags +- `C-c C-q` para añadir Tags +- Pueden ser predefinidas `#+TAGS: PHONE(p) COMPUTER(c) GARAGE(g)` +- Con `Tab` podemos teclear un tag no predefinido +- En mi caso para borrar todos los tag puedo user `C-q C-j enter` +- Las etiquetas (Tags) se heredan, aunque no las veas las etiquetas se + heredan en las ramas + +## Drawers (o cajones) + +Podemos definir un _drawer_ con una sintáxis (`:drawer_name:`) muy parecida a la de una etiqueta (_tag_). Pero los _drawers_ no van en la primera linea (como las etiquetas) y se terminan con un `:END:` + +Los _drawers_ son también plegables, así que podemos usarlos como un nivel más de plegado dentro de la etiqueta. Pero los más utilizados son los predefinidos `:PROPERTIES:` y `LOGBOOK` + +En el cajón de `:PROPERTIES:` se guardan las propiedades que vayamos añadiendo a nuestra entrada en el fichero `.org` + +En el cajón de `:LOGBOOK:` se guardan las notas de progreso o cambios de estado de nuestra entrada. + +### Log. Notas asociadas al `:LOGBOOK:` +- `C-c C-z` Para escribir una nota asociada a una entrada, la cerramos con `C-c C-c` +Podemos configurar si las notas se guardan en un _drawer_ o no con las opciones +- En la cabecera: `#+STARTUP: logdrawer / nologdrawer` +- Añadiendo a una rama la _Property_: `LOG_INTO_DRAWER` + +### Registrando los cambios de estado + +Cuando especificamos los estados en la cabecera podemos especificar que hacer al entrar y salir del estado. + +```org-mode +#+SEQ_TODO: Next(n) TODO(t@/!) Waiting(w) Someday(s) | DONE(d) CANCELLED(c) +``` + +`@` implica grabar un timestamp y una nota al entrar en el estado +`!` implica un timestamp al salir del estado +`#+STARTUP: logdone` Graba timestamp al pasar de un estado `TODO` a un estado `CLOSE` + +También hay opciones para grabar los reschedules `org-log-reschedule` + +## Archivado + +Las tareas se pueden archivar internamente (sin quitarlas de su fichero `.org` original) o moviéndolas a un fichero externo. + +El archivado interno supone pegar una etiqueta `:ARCHIVE:` con la combinación `C-c C-x a`. Una tarea archivada ya no aparecerá en las vistas de la agenda. + +Para el archivado externo podemos configurar el fichero de destino de varias maneras: + +* Definiendo un fichero de archivo en la cabecera con, por ejemplo (valor por defecto), `#+ARCHIVE: %s_archive::`, donde `%s` es el nombre de nuestro fichero `.org` +* Definiendo un fichero de archivo en una rama con: + +```org-mode + :PROPERTIES: + :ARCHIVE: ::* + :END: +``` + +* Definiendo los objetivos de archivado a nivel global con la variable `org-refile-targets` que permite establecer una lista de ficheros objetivo. Esta sería una manera _indirecta_ de archivar tareas. Ya que no es una operación de archivo propiamente dicha + +Para las dos primeras opciones el atajo de teclado sería `C-c$` para activar el archivado. + +Para la última opción invocaríamos __refile__ con el atajo `C-c C-w` + +## Fórmulas matemáticas + +Si especificamos la opción + +```orgmode +#+STARTUP entitiespretty +``` + +Se renderizarán las entidades LaTeX (probar a escribir por ejemplo: `\alpha + \beta`) + +La opción `C-c C-x C-l` invoca a `org-toggle-latex-preview` + +El paquete `org-fragtog` parece muy interesante para esto. + +## Links + +* [Guia](https://orgmode.org/orgguide.pdf) +* [Youtube List](https://www.youtube.com/watch?v=15w3I6MwCfs&list=PLVtKhBrRV_ZkPnBtt_TD1Cs9PJlU0IIdE&index=2) +* [Tutorials](https://orgmode.org/worg/org-tutorials/) +* [Mastering emacs](https://www.masteringemacs.org/) + +### Revisar + +* [org-math](https://github.com/dryman/org-math) como tomar apuntes de matemáticas en _org-mode_ +* [Latex for the impatients](https://karthinks.com/software/latex-input-for-impatient-scholars/): como teclear LaTeX rápido. +* [org-clock-remainder](https://github.com/inickey/org-clock-reminder): un módulo para configurar avisos en `org-mode` +* [Use org-mode to create LaTeX documents](https://opensource.com/article/20/4/emacs-org-mode) +* [Una serie de artículos con configuraciones de Emacs](https://lucidmanager.org/productivity/more-productive-with-emacs/) +* [Ricing Emacs](https://lucidmanager.org/productivity/ricing-org-mode/) +* Fniessen /org-mode html themes/ + * [cheatsheet](https://github.com/fniessen/refcard-org-mode) + * [Repo in github](https://github.com/fniessen/org-html-themes) +* Olmon /org-mode html themes collection/ + * [la página](https://olmon.gitlab.io/org-themes/) + * el [repo](https://gitlab.com/OlMon/org-themes/) en gitlab + +## chuleta de comandos + +##### Esquema básico + +| Atajo | Entorno | Significado | +|:-------------------|:-----------|:------------------------| +| `Tab` | Cabecera | Colapsa/Expande | +| `S-Tab` | Cualquiera | Colap/Exp. Global | +| `M-right`/`M-left` | Cabecera | Sección cambia de nivel | +| `M-Up`/`M-Down` | Cabecera | Sección cambia posición | +| `C-c C-w` | Cabecera | Sección cambia de rama | + + +##### Lista de tareas + +| Atajo | Entorno | Significado | +|:-------------------|:-------------|:-----------------------------| +| `S-Right`/`S-Left` | Cabecera | Ciclar estado 'todo' | +| `C-c C-t` | Cabecera | Cambiar estado 'todo' | +| `C-s` | Cabecera | Agendar tarea | +| `C-d` | Cabecera | _Deadline_ | +| `C-c a` | Cualquiera | Vista de agenda (int.) | +| `S-f` | Vista Agenda | Act./Desactiva _follow mode_ | +| `f`/`b` | Vista Agenda | Adelante atrás | +| `q` | Vista Agenda | Salir de agenda | + +###### checklist + +| Atajo | Entorno | Significado | +|:------------|:----------------|:------------| +| `C-S-enter` | Añade una linea | | + +##### Misc + +| Atajo | Entorno | Significado | +|:----------|:--------|:--------------------------------| +| `C-c C-c` | Varios | "Ejecutar"" esa linea o sección | diff --git a/content/posts/notes_general/notes_proxmox.md b/content/posts/notes_general/notes_proxmox.md new file mode 100644 index 0000000..9a0b143 --- /dev/null +++ b/content/posts/notes_general/notes_proxmox.md @@ -0,0 +1,691 @@ +--- +weight: 4 +title: "Apuntes de Proxmox VE" +date: 2022-03-23T20:02:31+0100 +draft: false +summary: "Apuntes de Proxmox VE" +categories: + - notes +tags: + - proxmox + - docker + - traefik +--- + + +Estos apuntes son de _Proxmox VE_ un gestor de virtualizaciones muy potente implementado sobre Debian 11 Bullseye en la versión actual, la +7.1-11 en el momento de escribir esto. + +(Actualizado el 2022-03-23) + + +# Proxmox VE + +_Promox VE_ es un software libre producto de _Proxmox Technologies_; una empresa austríaca que suministra productos de software libre (obvio). _Proxmox VE_, _Proxmox Backup Server_ y _Proxmox Mail Gateway_, como vemos en [su página web](https://www.proxmox.com) + +{{< admonition type=abstract title="Referencias" open=true >}} + +- [Blog de drivemeca](https://drivemeca.blogspot.com/2017/06/el-servidor-perfecto-proxmox-ve.html) + +{{< /admonition >}} + + +## Motivación + +Inicialmente configuré el mini-PC (Beelink Gemini T4) con Debian Server y Docker para poder implementar micro-servicios e instalar _Home Assistant_ (dockerizado). + +Como sentía curiosidad por ver como funciona la versión _Supervised_ de HAss (_Home Assistant_) empecé a mirar opciones para manejar máquinas virtuales en un servidor _headless_. Estudié la posibilidad de instalar Qemu, pero lo descarté por que tiene dependencias con Xorg que no me convencen. Varias personas me han hablado maravillas de _Proxmox_ y como tengo toda la instalación del mini-pc bien documentada y con copias de seguridad me he decidido a cambiar a _Proxmox_. Al fin y al cabo es saltar desde Debian a Debian con Proxmox, no debería ser muy traumático. + +## Instalación de Proxmox + +Preparamos un USB con _Etcher_ con la ISO descargada de la [página web](https://www.proxmox.com/en/downloads) + +Arrancamos el minipc desde el USB (pulsando `F7`) y lanzamos la +instalación. Durante la instalación sólo he tenido que configurar: + +__Parámetros de LVM__ + +El disco SSD del minipc es de 256Gb + +`Filesystem` +: Escogemos `ext4` + +`hdsize` +: Lo fija el instalador autmáticamente + +`swapsize` +: Lo he fijado a 16Gb (creo que es mala idea en un SSD, si vemos que empieza a tirar de swap habrá que plantearse quitarla) + +`maxroot` +: escogí 60Gb, este es el tamaño de la partición `root` que tendrá + formato `ext4` + +`minfree` +: la parte del disco que quedará disponible sin usar, he dejado reservados 60Gb + +`maxvz` +: sería el límite máximo para el LVM `data` lo he puesto a 200 para + que no limite, todo el disco menos los segmentos especificados en + las opciones anteriores será usado para `data` + +__Parámetros de Red__ + +Especificamos la IP, el nombre del servidor (con dominio, aunque te lo puedes inventar claro) y el DNS + +__Timezone__ + +Especificamos en que zona horaria estamos. + +Con esto se completa la instalación y el sistema se reiniciará. + +Una vez reiniciado el sistema podremos conectarnos al PVE via web. + +También podemos conectarnos via _ssh_. Como tengo muchas claves generadas para distintas conexiones en mi ordenador, si nos conectamos sin más nos dará error de exceso de intentos (probará todas las claves). Para las primeras conexiones tendremos que usar: + +```bash +ssh -o PreferredAuthentications=password root@ 25 +``` + +## Postinstalación + +Lo primero que tenemos que hacer, terminada la instalación es ajustar los orígenes del software y actualizar todos los paquetes. Es decir, lo que se hace siempre en una instalación de Debian. + +Cambiamos el origen de sw `pve-enterprise` por el `pve-no-subscription` (a menos que queramos alguna de las opciones de pago de _Proxmox_ que sería lo recomendable en un entorno de producción). + +Comentamos la linea en el fichero `/etc/apt/sources.list.d/pve-enterprise.list` + +Y creamos un nuevo fichero: + +```bash +echo "deb http://download.proxmox.com/debian/pve buster pve-no-subscription" > /etc/apt/sources.list.d/pve-no-suscription.list +``` + +En el fichero `/etc/apt/sources.list` cambiamos los orígenes de software de Debian, e incluimos `contrib` y `non-free`: + +```bash +deb http://deb.debian.org/debian/ buster main contrib non-free +deb-src http://deb.debian.org/debian/ buster main contrib non-free + +deb http://security.debian.org/debian-security buster/updates main contrib non-free +deb-src http://security.debian.org/debian-security buster/updates main contrib non-free + +# buster-updates, previously known as 'volatile' +deb http://deb.debian.org/debian/ buster-updates main contrib non-free +deb-src http://deb.debian.org/debian/ buster-updates main contrib non-free +``` + +Actualizamos todos los paquetes con: + +```bash +apt update +apt upgrade +``` + +Podemos actualizar todos los paquetes de _Proxmox_ con el comando +`pveam update`, o hacerlo desde el interfaz gráfico, que es más +ilustrativo para las primeras veces (lo vemos luego) + +Una vez actualizado el sistema operativo procedemos a instalar nuestros paquetes habituales. + +### Instalamos _git_ y _etckeeper_ + +Suelo instalar _etckeeper_ para tener un histórico automático de cambios en el `/etc` + +Como `root` ejecutamos: + +```bash +apt install git +git config --global user.email "whaterver@mail.com" +git config --global user.name "Name Surname" +apt install etckeeper +``` + +Si quieres ver el histórico de cambios de `/etc` solo tienes que ejecutar como `root`: + +```bash +cd /etc +git log +``` + +{{< admonition type=tip title="Problemas con default locales" open=false >}} + +Si (como a mi) te dan problemas los `locales` (suele pasar en la instalación de _Debian Server_ veras que `apt` y `apt-get` protestan por la configuración de `locales`) se puede arreglar fácilmente sin más que ejecutar `sudo dpkg-reconfigure locales` Puedes aprovechar la ejecución para dejar generado algún `locale` adicional. + +Estos son los que yo he dejado configurados en mi server (se pueden consultar con el comando `locale`): + +```bash +## Antes de la reconfiguración +LANGUAGE = (unset), +LC_ALL = (unset), +LC_MONETARY = "es_ES.UTF-8", +LC_ADDRESS = "es_ES.UTF-8", +LC_TELEPHONE = "es_ES.UTF-8", +LC_NAME = "es_ES.UTF-8", +LC_MEASUREMENT = "es_ES.UTF-8", +LC_IDENTIFICATION = "es_ES.UTF-8", +LC_NUMERIC = "es_ES.UTF-8", +LC_PAPER = "es_ES.UTF-8", +LANG = "en_US.UTF-8" + +## Después de la reconfiguración +LANG=en_US.UTF-8 +LANGUAGE= +LC_CTYPE="en_US.UTF-8" +LC_NUMERIC=es_ES.UTF-8 +LC_TIME="en_US.UTF-8" +LC_COLLATE="en_US.UTF-8" +LC_MONETARY=es_ES.UTF-8 +LC_MESSAGES="en_US.UTF-8" +LC_PAPER=es_ES.UTF-8 +LC_NAME=es_ES.UTF-8 +LC_ADDRESS=es_ES.UTF-8 +LC_TELEPHONE=es_ES.UTF-8 +LC_MEASUREMENT=es_ES.UTF-8 +LC_IDENTIFICATION=es_ES.UTF-8 +LC_ALL= +``` + +{{< /admonition >}} + + + +### Varios programas instalados en Debian 10 + +---- + +__OJO__: `apt` instala por defecto los paquetes recomendados, si no +quieres que eso ocurra usa `apt-get` o `aptitude` con las opciones +correspondientes. + +---- + +Instalamos varias librerías de compresión: + +```bash +apt install rar unrar zip unzip unace bzip2 lzop p7zip p7zip-full +``` + +Instalamos algunas utilidades: + +```bash +apt install most mc tree neofetch tmux aptitude htop +``` + +Instalamos sudo: + +```bash +apt install sudo +``` + +Cualquier usuario que pertenezca al grupo `sudo` podrá ejecutar +comandos con privilegios de _root_. (podemos añadirlos, por ejemplo, con `gpasswd -a sudo`) + +### Configuramos el acceso via _ssh_ + +El PVE incluye su propio cortafuegos. Tendremos que revisar su configuración más tarde. + +#### Creamos un usuario de administración + +Me voy a crear un usuario de administración para no tener que hacer login con `root`. El usuario va a estar en el grupo `sudo` así que va a ser todopoderoso, pero tendrá que saber su password para poder "sudar". + +```bash +# sudo adduser administrator +# sudo gpasswd -a administrator sudo +$ adduser --uid=1111 hostadmin +gpasswd -a hostadmin sudo +``` + + +#### Parámetros de login _ssh_ + +Vamos a: + +* Deshabilitar el login de root via ssh (queremos que todos entren con + su usuario persona y después usen sudo) +* Deshabilitar el login con contraseña (queremos que todo el mundo use + autenticación por clave pública) + +Lo primero de todo es configurar y probar nuestro acceso con clave ssh, copiamos nuestra clave al servidor, debemos copiarla tanto para el usuario `root` como para el usuario administrador que hayamos creado: + +`ssh-copy-id -o IdentitiesOnly=yes -i ~/.ssh/mykey.pub @` + +Y probamos a conectarnos con la clave. + +`ssh -i ~/.ssh/mykey @` + +Si todo va bien podemos añadir (en nuestro pc, no en el servidor _Proxmox_) la información de conexión a nuestro fichero `~/.ssh/config` + +```cfg +Host + HostName + User + Port 22 + IdentityFile ~/.ssh/ +``` + +El fichero que controla la configuración del demonio _ssh_ en nuestro +servidor es `/etc/ssh/sshd_config` tenemos que asegurarnos de añadir +las siguientes lineas: + +```cfg +PermitRootLogin no +PasswordAuthentication no +ChallengeResponseAuthentication no +UsePAM no +``` + +Después de cambiar la configuración reiniciamos el servicio con `sudo +systemctl restart ssh` + +Tras el reinicio no deberíamos poder hacer login con `root` via ssh. + +#### _ssh-agent_ + +Esto es un apunte uso para trabajar cómodo con Gitlab desde el servidor _Proxmox_, con las claves _ssh_ instaladas en el servidor. + +```bash +eval `ssh-agent` +ssh-add .ssh/dev_rsa +ssh-add -l +ssh-add -L +``` + +### Disco duro interno + +{{< admonition type=info title="Referencias de HDD" open=false >}} + +- [Opciones para `ext4`](https://man7.org/linux/man-pages/man5/ext4.5.html) +- [Un buen resumen](https://www.howtogeek.com/444814/how-to-write-an-fstab-file-on-linux/) +- [Filtrar discos del LVM](https://forum.proxmox.com/threads/disk-prevent-from-spinning-down-because-of-pvestatd.53237/) +- [Crear un servicio systemd con las optimizaciones](https://unix.stackexchange.com/questions/80437/how-can-i-run-an-hdparm-command-after-boot-and-resume-on-fedora-19) + +{{< /admonition >}} + + +En mi caso el disco duro interno que quiero añadir está mapeado en `/dev/sda`, se trata de un disco de 2Tb y queremos hacer dos particiones: una para almacenar backups, templates, snapshots, etc. etc. Y otra partición (mucho más grande) que usaremos para crear discos para las VM. + +Con nuestro programa de particionado favorito creamos las dos particiones. Yo tengo disponibles `fdisk` y `parted` pero nada impide instalar el que nos guste. + +He creado una partición de 250 Gb para almacenamiento y el resto de la capacidad para discos de VM. + +#### Creando el "directorio" + +Navegamos hasta nuestra máquina host debajo del `Datacenter` localizamos el interfaz `Disks::Directory` y creamos el nuevo directorio apuntando a la partición que hemos creado en el paso anterior (`/dev/sda1` en mi caso), con el sistema de ficheros que nos guste (lo he dejado como ext4) y le damos un nombre descriptivo (para mi `hdd_directory`). + +#### Creado el volumen lógico + +Navegamos a `LVM` debe estar muy cerca, por encima de `Directory`. Seleccionamos el botón `Create Volume Group`: sólo tenemos que especificar la partición a utilizar y un nombre. Y eso es todo ya tenemos un VG donde podremos crear discos para nuestras máquinas virtuales. + + +### Discos duros USB externos + +Si son discos nuevos ejecutamos estos pasos +1. Conectamos los discos al mini-pc +1. Comprobamos los discos con `lsblk -p |grep disk` o con `sudo fdisk -l` +1. Si es necesario los podemos formatear a _ext4_ con el comando `sudo mkfs.ext4 /dev/sdX` (donde X será la letra que corresponda a nuestros discos) +1. Creamos el/los puntos de montaje de nuestros discos, p.ej. `/mnt/usbA`, con el comando `sudo mkdir /mnt/usbA` +1. Montamos el/los discos con el comando `sudo mount /dev/sdX /mnt/usbA` + +Para dejar los discos duros mapeado permanentemente en nuestro servidor debemos modificar el fichero `/etc/fstab`: + +1. Averiguamos la identidad de los discos con `sudo blkid` +2. Añadimos las lineas correspondientes al fichero `/etc/fstab` que serán de esta forma (cambia los UUID): + + ```fstab + UUID=1a716b79-b39a-49bf-bae6-4992376022e0 /mnt/usbA ext4 defaults 0 2 + ``` + +#### Discos duros externos que no dejan de funcionar + +Mis discos duros externos son un par de discos USB portátiles, dudo mucho que puedan estar funcionando continuamente. + +_Proxmox_ escanea continuamente todos los discos duros del sistema. Si queremos que los discos duros se duerman tenemos que cambiar la configuración en el fichero `/etc/lvm/lvm.conf`. Concretamente cambiamos la linea: + +`global_filter = [ "r|/dev/sd(b|c)|" , "r|/dev/zd.*|", "r|/dev/mapper/pve-.*|" "r|/dev/mapper/.*-(vm|base)--[0-9]+--disk--[0-9]+|"]` + +Con la primera entrada (`"r|/dev/sd(b|c)|"`) estamos indicando que se ignore los discos `/dev/sdb` y `/dev/sdc`. Con esto perdemos también la posibilidad de crear volúmenes _lvm_ en estos discos, pero a mi de momento no me interesa. + +-------------------- + +### _dynhost_ en OVH + +Instalado [el script de actualización de dynhost](https://github.com/BorisPAING/dynhost-ovh) en OVH. + +Es necesario tener disponible `dig` así que instalamos: + +```bash +sudo apt install dnsutils +``` + +Después basta con programar el *script* periódicamente en el crontab +de `root` con las credenciales de OVH. + +### Comprobamos la instalación de python + +```bash +apt install python-all-dev python3-all-dev +apt install virtualenv python3-virtualenv virtualenvwrapper +``` + +### Instalado el zsh + +Lo he dejado instalado siguiendo [la guía](https://gitlab.com/salvari/linuxmint_ulyana_20). + + +## Revisión de la instalación de Proxmox + +* Nos conectamos al interfaz web con el usuario `root` y la contraseña que hemos establecido durante la instalación. +* Clicamos en nuestro nodo (el servidor en el árbol de la izquierda) y comprobamos las actualizaciones pendientes (_Updates_) + +## Instalación de una máquina virtual con Home Assistant + +{{< admonition type=info title="Referencias" open=false >}} + +- [Install HAss on Proxmox](https://community.home-assistant.io/t/installing-home-assistant-os-using-proxmox-7/201835) (by Kanga-Who) + +{{< /admonition >}} + +Una de las misiones principales de mi servidor Proxmox es soportar la VM de Homeassistant. Crear la máquina virtual es muy fácil gracias a los scripts creados por _Whiskerz007_ y _Kanga-Who_. Vamos a seguir la página de este último. + +Antes de arrancar la VM conviene revisar el hardware asignado y las opciones de la VM (yo solo tuve que ajustar la memoria asignada) + + +## Instalación de Docker + +{{< admonition type=info title="Referencias" open=false >}} + +- [Running Docker on Proxmox](https://danthesalmon.com/running-docker-on-proxmox/) + +{{< /admonition >}} + + +Tendríamos tres formas de instalar Docker en nuestro sistema _Proxmox_ + +- Instalándolo en el S.O. Debian base +- Como contenedor LXC +- Como máquina virtual + +Mi intención era usar el primer método e instalar el _Docker_ sobre Debian. Este método no debería usarse nunca en producción pero para el uso que yo quiero darle parece aceptable. Lo he instalado aunque, de momento, dejo el servicio _Docker_ desactivado. + +El segundo método que he usado es crear una máquina virtual con _Alpine Linux_ e instalar _Docker_ sobre esa máquina. + +### Instalar Docker en Debian + +Vamos allá + +```bash +apt update +apt install apt-transport-https ca-certificates curl gnupg2 software-properties-common +add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable" +apt-key adv --keyserver ha.pool.sks-keyservers.net --recv-keys 7EA0A9C3F273FCD8 +apt update +apt-cache policy docker-ce +apt install docker-ce + +systemctl status docker + +gpasswd -a username docker +``` + +Y ya tenemos docker instalado. + +Ahora completamos con la instalación de _docker-compose_. + +Primero comprobamos la versión. En el momento de escribir esto es la +1.28.5. Con el siguiente comando descargamos el binario +correspondiente a nuestro sistema en `/usr/local/bin` y le damos +permisos de ejecución. + +```bash +curl -L https://github.com/docker/compose/releases/download/1.28.5/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose +chmod +x /usr/local/bin/docker-compose +``` + +Para parar el servicio _Docker_ y dejarlo desactivado (con los alias de zsh): + +```bash +scs docker # systemctl status docker +scsp docker # systemctl stop docker +scd docker # systemctl disable docker +scs docker # systemctl status docker +``` + +### Instalar Docker en una máquina virtual + +{{< admonition type=info title="Referencias" open=false >}} + +- [How to install docker on Alpine Linux VM](https://nubcakes.net/index.php/2019/03/15/how-to-install-docker-on-alpine-linux-vm/) +- [How to install Docker CE on Alpine Linux](https://www.how2shout.com/how-to/how-to-install-docker-ce-on-alpine-linux.html) +- [Alpine Linux Wiki: Docker](https://wiki.alpinelinux.org/wiki/Docker) +- [Howto Alpine Wall](https://wiki.alpinelinux.org/wiki/How-To_Alpine_Wall) + +{{< /admonition >}} + + +* Descargamos la imagen ISO de Alpine optimizada para sistemas virtuales desde [aquí](https://alpinelinux.org/downloads/) +* Subimos la imagen ISO a nuestro nodo en _Proxmox_ (En el árbol vamos a: __nodo → local(nodo) → Iso Images__) +* Creamos una máquina virtual (he dejado prácticamente los valores por defecto, asignando 32Gb de disco duro) +* Una vez arrancada la máquina ejecutamos `setup-alpine` y vamos asignando las [opciones de instalación](https://wiki.alpinelinux.org/wiki/Installation#Questions_asked_by_setup-alpine): + * Teclado `es-winkeys` + * Hostname: `srvname` + * Asignamos IP estática + * Es necesario tener acceso a internet para asignar los repos de software de Alpine, así que hay que asignar _gateway_ y _dns server_. + * Escogemos "sys" para la estructura de disco + * Damos permiso para usar y borrar el disco + +Una vez instalado y rearrancado el sistema editamos `/etc/apk/repositories` y habilitamos `comunity`. + +__¡Ojo!__ si habilitas `edge` estarás habilitando la rama de desarrollo de Alpine y no es lo que queremos ahora mismo. + +Actualizamos los paquetes del sistema con `apk update`. + +Instalamos `apk add qemu-guest-agent` + +{{< admonition type=info title="Qemu-guest-agent" open=true >}} + +El _qemu-guest-agent_ sirve para que _Proxmox_ pueda parar la VM y/o congelar su sistema de ficheros de forma segura. Esto facilita mucho varias operaciones como por ejemplo hacer _backups_ de las VM + +{{< /admonition >}} + + +Editamos el fichero `/etc/init-d/qemu-guest-agent`, su última línea tiene que ser: + +`command_args="-m ${GA_METHOD:-virtio-serial} -p ${GA_PATH:-/dev/vport2p1} -l /var/log/qemu-ga.log -d"` + +Añadimos el agente al arranque: `rc-update add qemu-guest-agent boot` + +Paramos la máquina virtual y en el interfaz _Proxmox_ cambiamos las +opciones para habilitar el `Qemu Guest Agent` + +Volvemos a arrancar la VM (Importante reiniciar desde el Proxmox) y comprobamos que en el `Summary` el `qemu-guest-agent` se conecta correctamente. + +{{< admonition type=warning title="Qemu-guest-agent: fallo al crear el canal" open=false >}} + +Mi `qemu-guest-agent` fallaba al arrancar con los siguientes mensajes en `/var/log/qemu-ga.log` + +```log +1618081791.809004: critical: error opening channel: No such file or directory +1618081791.809056: critical: error opening channel +1618081791.809070: critical: failed to create guest agent channel +1618081791.809082: critical: failed to initialize guest agent channel +``` + +Por alguna razón la máquina que he creado no tiene el dispositivo `/dev/vport1p1`. El dispositivo en mi máquina es el `/dev/vport2p1`. Evidentemente el agente falla hasta que corregimos el fichero `/etc/init-d/qemu-guest-agent` + +{{< /admonition >}} + + +A continuación procedemos a instalar `sudo` con `apk add sudo` y con `visudo` habilitamos sudo para el grupo `wheel` (descomentar la linea correspondiente) + +Creamos grupo y usuario para _Docker_: + +```bash +addgroup -g 150 docker +addgroup dockadmin +adduser -G dockadmin dockadmin +adduser dockadmin wheel +adduser dockadmin docker +cat /etc/passwd +``` + +Instalamos _Docker_: + +```bash +apk add docker +rc-update add docker boot +service docker start +service docker status +``` + +Y lo probamos: + +`docker run --rm hello-world` + +Como todo va bien instalamos también el `docker-compose` + +```bash +sudo apk add docker-compose +``` + +#### Cortafuegos + +Instalamos el UFW + +```bash +sudo apk add ufw +``` + +Y hacemos una configuración inicial + +```bash +sudo ufw status verbose # Comprobamos que está deshabilitado +sudo ufw app list # comprobamos que apps soporta +sudo ufw default deny incoming # le decimos que por defecto no acepta nada entrante +sudo ufw default allow outgoing # por defecto permitimos conexiones salientes +sudo ufw allow ssh # permitimos conexiones ssh +sudo ufw enable # lo habilitamos +sudo ufw status verbose # vemos el estado del cortafuegos +``` + +#### Instalando nuestros primeros contenedores + +Una vez comprobado que funciona todo añadimos una configuración básica con _Traefik_ y _Portainer_ como comentamos en [el ejemplo 3 de los apuntes de _Traefik_]({{< ref "notes_traefik/#ejemplo03-una-configuraci%C3%B3n-sencilla-para-empezar-con-traefik-en-producci%C3%B3n" >}}) + + +#### Almacenamiento para Docker + +* [Referencia](https://www.hiroom2.com/2018/08/29/alpinelinux-3-8-sshfs-en/) + +Como hemos sacado los discos duros externos del Proxmox no podemos crear volúmenes en los mismos. Para darle espacio de almacenamiento a los contenedores en esos discos duros vamos a hacer algo un poco rebuscado: montar un directorio del disco externo via `sshfs`. + +En nuestra máquina virtual Alpine: + +1. Instalamos los paquetes `sshfs` y `util-linux`, además configuramos el sistema para que se carge el módulo del kernel `fuse` + +```bash +sudo apk add sshfs util-linux +sudo modprobe fuse +echo fuse | sudo tee -a /etc/modules +``` + +2. Creamos el directorio local (en nuestra VM) donde montaremos el disco duro externo. + +```bash +mkdir $HOME/mnt/store +``` + +3. Creamos una clave con `ssh-keygen` que nos permita facilitar la conexión a la máquina virtual. + +Una vez creada la clave , podriamos crear el fichero `~/.ssh/config` y montar la unidad por comando con el usuario administrador de docker (por ejemplo). Pero nos interesa más montar el directorio del disco duro en el arranque de la máquina virtual dejándolo especificado en el fichero `/etc/fstab` + +Generalmente no permitimos el acceso del usuario `root` via ssh en ningún sistema, así que tendremos que especificar el montaje con un usuario. Usaremos el usuarios `dockadmin` del servidor host y el usuario del mismo nombre que creamos en la máquina virtual de Docker. + +4. Añadimos la clave pública al fichero `~/.ssh/authorized_keys` del usuario `dockadmin` en la VM. (depende de tu escenario, yo lo hice pasando por mi ordenador personal, desde donde estoy configurando todo) + +5. Creamos una entrada para la VM en el fichero `/etc/hosts` + +```bash +sudo echo " hostname" >> /etc/hosts +``` + +6. Habilitamos `netmount` + +```bash +sudo rc-update add netmount +``` + +7. Añadimos la linea al fichero `/etc/fstab` + +```fstab +dockadmin@sirio:/mnt/usbA/Work/dockerStore:/home/dockadmin/mnt/store fuse.sshfs _netdev,identityfile=/home/dockadmin/.ssh/id_mount,allow_other,uid=dockeradmin,gid=dockeradmin 0 0 +``` + + + +## Recetillas + +### Quitando el mensaje de "invalid subscription" + +{{< admonition type=info title="Referencia" open=false >}} + +- [Remove Proxmox subscription warning](https://johnscs.com/remove-proxmox51-subscription-notice/) + +{{< /admonition >}} + +Editamos el fichero `/usr/share/javascript/proxmox-widget-toolkit/promoxlib.js` + +Localizamos la cadena `No valid subscription` y cambiamos al función para que quede así: + +```javascript +void({ //Ext.Msg.show({ + title: gettext('No valid subscription'), +``` + +Reiniciamos el servicio con `systemctl restart pveproxy.service` + + + + + + diff --git a/content/posts/notes_general/notes_python.md b/content/posts/notes_general/notes_python.md new file mode 100644 index 0000000..7f1a817 --- /dev/null +++ b/content/posts/notes_general/notes_python.md @@ -0,0 +1,2264 @@ +--- +weight: 4 +title: "Apuntes de Python" +date: 2020-05-29T12:35:51+0100 +draft: false +summary: "Notas sobre python" +categories: + - notes +tags: + - python + - programacion +--- + +{{< admonition type=warning title="Work in progress" open=true >}} + +Apuntes de Python, muy incompletos. + +{{< /admonition >}} + +## Gestores de paquetes + +**pip** + +: El gestor de paquetes por defecto de python + +**pipx** + +: Un instalador pensado para aplicaciones CLI en Python, se encarga de instalar cada aplicación en un _virtualenv_ independiente. +: Cosas instaladas con `pipx` en mi sistema son por ejemplo: `pypisearch`, `ranger` o `kikit` + +: Los _virtualenv_ creados por **pipx** quedan en `~/.local/pipx/venvs` + +**conda** + +: El instalador de las distribuciones _Anaconda_ y _Miniconda_ + + +## Gestores de entornos virtuales + +En cualquier distribución de Linux disponemos de un _Python del +Sistema_, es el _Python_ que viene instalado por defecto con el S.O. (podemos averiguar cual es ejecutando: `which python` y/o `python -V`) En mi caso concreto, usando [Linux Mint](https://linuxmint.com/) el Python del sistema es el 2.7.18. Una versión anticuada, pero que es mejor no tocar ya que habrá partes del sistema operativo que dependan de ella. + +Podemos tener más versiones de Python instaladas en nuestro S.O. (yo ahora mismo tengo disponibles `python3`, `python3.8` y `python3.9`) pero solo una de ellas es el _Python del Sistema_, el Python que se ejecuta por defecto. + +Instalar paquetes de Python en el _Python del Sistema_ **no es nada recomendable**. Si instalamos versiones incompatibles de paquetes o actualizamos versiones instaladas podemos perturbar el funcionamiento de alguna parte del propio sistema operativo. Desinstalar paquetes en Python siempre es una aventura (a menudo imposible) así que si tenemos problemas la solución puede ser extremadamente complicada (a mi ya me tocó reinstalar Linux entero por enredar sin saber). + +{{< admonition type=tip title="Módulos Python que si que instalo" open=true >}} +Hay algunos paquetes, como veremos en el siguiente punto, que si que nos conviene tenerlos siempre instalados. Son justo los que nos facilitan la gestión de los _virtualenvs_, como por ejemplo `venv`, `virtualenv` o `pipx`. +{{< /admonition >}} + +Además de todo lo anterior es muy probable que la versión del *Python del Sistema* no sea la versión que queremos usar con nuestro proyecto. O podemos estar interesados en usar varias versiones diferentes de *Python*, ya sea para desarrollo o para ejecutar distintas aplicaciones. + +Un gestor de entorno python nos permitirá: + +1. Instalar python en un entorno aislado +2. Instalar multiples versiones de python; cada una en su propio entorno, de forma que no interfieran entre si +3. Instalar distintas combinaciones de paquetes +4. Cambiar de versión de python sin más que cambiar de entorno + +### virtualenv y virtualenvwrapper + +_virtualenv_ es probablemente la herramienta más básica para crear entornos virtuales para Python y _virtualenvwrapper_ es un _frontend_ que facilita mucho su uso. + +La idea es muy simple, se crea una instalación de Python independiente en un directorio dedicado y se modifica el `PATH` para que ese Python sea prioritario. + +_virtualenvwrapper_ centraliza todos los entornos virtuales (es decir los directorios con los distintos python), en un único directorio de entornos (especificado con `$WORKON_HOME`) + +En el Python 3 que viene con mi Linux Mint yo instalo: + +```bash +sudo apt install python-all-dev +sudo apt install python3-all-dev +sudo apt install virtualenv virtualenvwrapper python3-virtualenv +``` + +En el fichero `~/.profile` añadimos: + +```bash +# WORKON_HOME for virtualenvwrapper +if [ -d "$HOME/.virtualenvs" ] ; then +WORKON_HOME="$HOME/.virtualenvs" +fi +``` + +Además en `zsh` uso el bundle `virtualenvwrapper` y tengo parcheado el _prompt_ del tema del zsh y el _prompt_ del _zsh-git-prompt_ (Ver [dotfiles](https://gitlab.com/salvari/usrcfg_legion_ulyana)) para visualizar en el _prompt_ el entorno virtual activo (si hay alguno activado claro) + +Este _bundle de zsh_ también se encarga de activar automáticamente el entorno virtual si cambiamos a un directorio de proyecto con el mismo nombre (se considera directorio de proyecto si está controlado por git). Aunque también hay formas de asociar un directorio de proyecto al crear un entorno virtual, como veremos. + +```bash +#------------------- +# virtualenv +virtualenv # Crea el entorno como un directorio en el + # directorio donde lo hemos invocado (es típico llamarlo .env) +source env/bin/activate # Activar el entorno +deactivate # desactivar el entorno + + +#------------------- +# virtualenvwrapper +mkvirtualenv -p /usr/bin/python3 # Crear un entorno virtual y activarlo +mkvirtualenv -p /usr/bin/python3 -a # Crear un entorno virtual y asociar el directorio de proyecto +deactivate # Salir del entorno virtual +lsvirtualenv # ver la lista de entornos virtuales +workon # activar un entorno ya definido +rmvirtualenv # borrar un entorno virtual +cpvirtualenv # copiar un entorno virtual +lssitepackages # listar paquetes instalados en el entorno activo +``` + +### pyenv + +{{< admonition type=danger title="Importante" open=true >}} + +No confundir con `pyenv` con `pyvenv` + +{{< /admonition >}} + + + +[_pyenv_](https://github.com/pyenv/pyenv#readme) es mucho más potente que _virtualenv_ y además podemos integrar tanto _virtualenv_ como _virtualenvwrapper_ con _pyenv_. + +Mi ciclo de trabajo con _pyenv_: + +1. Instalar versiones de Python en mi máquina con _pyenv_ +1. Crear entornos virtuales basados en esas versiones, con el plugin _virtualenv_ de _pyenv_ +1. Asignar los entornos virtuales a proyectos (normalmente con `pyenv local ` + +Instalamos las dependencias en el sistema: + +```bash +sudo apt-get update +sudo apt-get install --no-install-recommends make build-essential \ +libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev \ +wget curl llvm libncurses5-dev xz-utils tk-dev \ +libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev +``` + +Y a continuación instalamos el `pyenv-bundle` en _Antigen_, añadiendo la linea: + +`antigen bundle mattberther/zsh-pyenv` + +a nuestro fichero `~/.zshrc` + +La instalación que hace el plugin de `zsh` es muy completa, instala _pyenv_ con todos los plugins. + +Evidentemente este método de instalación solo vale si usas `zsh`, [aquí](https://github.com/pyenv/pyenv-installer) tienes una alternativa (que no he probado) o puedes currarte una instalación siguiendo las instrucciones oficiales en [el github del proyecto pyenv](https://github.com/pyenv/pyenv#installation) + +`zsh-pyenv` se encargará de la instalación de `pyenv` + +Tenemos que asegurarnos de dejar configurada la activación de `pyenv` en nuestros fichero `.profile` y `.zshrc` (o `.bashrc`) + +En `~/.profile` añadimos las siguientes lineas (las lineas comentadas se dejan como referencia por que ya lo configura el plugin de zsh): + +```bash +# pyenv +if [ -d "$HOME/.pyenv" ] ; then + export PYENV_ROOT="$HOME/.pyenv" +# export PATH="$PYENV_ROOT/bin:$PATH" +# eval "$(pyenv init --path)" +fi +``` + +En `~/.zshrc` **no debería** ser necesario añadir nada por que se supone que el plugin de _oh-my-zsh_ se encarga de activar todo _pyenv_, pero en mi instalación el autocompletado de comandos de _pyenv_ solo funciona si añado este código: + +```bash +# pyenv activation +if command -v pyenv 1>/dev/null 2>&1; then + eval "$(pyenv init -)" +fi +``` + +Si quieres echar un ojo a las versiones de Python que puedes instalar en tu sistema ejecuta `pyenv install --list` (hay 490 versiones en el momento de escribir esto) + +```bash +pyenv commands # La lista de comandos disponibles (¡son muchos!) +pyenv --help # Ayuda para un comando +pyenv install --list # Versiones de python disponibles +pyenv versions # Versiones de python instaladas en nuestro sistema + +pyenv global # Establece la version de python global +pyenv local # Establece la versión de python local +pyenv shell # Establece la versión para el shell +pyenv virtualenv # Crea un entorno virtual basado en la versión especificada +``` + +### pyenv global + +Me he creado un entorno virtual para usar como _pyenv global_: +* Creamos un entorno basado en la versión oficial de Python más reciente (3.9.6 al escribir esto) +* En el nuevo entorno instalamos las últimas versiones de _pip_, _setuptools_, _pipx_, _virtualenv_ y _virtualenvwrapper_ + +```bash +pyenv virtualenv 3.9.6 ve_sys39 +pyenv which python +/home/salvari/.pyenv/versions/ve_sys39/bin/python +pip install --upgrade pip +pip install --upgrade setuptools +pip install --upgrade pipx +pip install --upgrade virtualenv virtualenvwrapper +``` + +{{< admonition type=danger >}} + +Parece que los plugins `pyenv` y `virtualenvwrapper` de `oh-my-zsh` no son compatibles. El plugin de `oh-my-zsh` para _virtualenvwrapper_ intenta activar el _virtualenvwrapper_ haciendo un `source virtualenvwrapper.sh`, pero ahora tenemos un _shimm_ que intercepta la llamada y no funciona correctamente, de hecho **aborta el terminal**. + +No parece aconsejable activar simultaneamente los dos plugins así que el plugin de `virtualenvwrapper` queda desactivado en el fichero _.zshrc_ +{{< /admonition >}} + +Para poder activar el _virtualenvwrapper_ correctamente via el shimm, añadimos el siguiente alias: + +```bash +# virtualenvwrapper activation +alias myvw='source `pyenv which virtualenvwrapper.sh`' +``` + +Esto funcionará correctamente siempre y cuando en el python activo tengamos instalado _virtualenvwrapper_ claro está. + +### pyenv y emacs + +**Referencias** + +* Una referencia muy completa [Getting started with lsp-mode for Python](https://www.mattduck.com/lsp-python-getting-started.html) + +En _Emacs_ usamos [LSP]^(Language Server Protocol). Para Python necesitamos instalar [python-lsp-server](https://pypi.org/project/python-lsp-server/) **Mucho ojo**, no hay que instalar Palantir ([python-language-server](https://pypi.org/project/python-language-server/)), que se considera obsoleta. + +Con el siguiente comando instalamos en nuestro Python activo (por ejemplo el _Python global_) el servidor `pylsp` y todos sus _providers_ (ver el [github del proyecto](https://github.com/python-lsp/python-lsp-server)) + +```bash +pip install 'python-lsp-server[all]' +``` + +Si queremos que nuestro editor Emacs edite código Python a plena potencia necesitaremos instalar este paquete en el Python activo del proyecto en el que trabajemos. + + +Para simplificar la creación de nuevos entornos creamos el alias `myve` que podemos invocar una vez creado nuestro nuevo entorno virtual para instalar los paquetes mínimos: + +```bash +myve(){ + if [ `pyenv which python` = "/usr/bin/python" ] + then + echo "This is main python. Abort installation" + else + echo "OK, looks like you are in some venv" + pip install --upgrade pip setuptools wheel pipx virtualenv virtualenvwrapper + pip install 'python-lsp-server[all]' + fi +} +``` + +### pyenv y conda + +Ojo, por que los entornos virtuales de Anaconda y Miniconda parece que hay que crearlos con `conda` para que funcionen bien. (ver [artículo](https://www.soudegesu.com/en/python/pyenv/anaconda/)). + +Supongamos que tenemos instalada la versión `anaconda3-2021.11`. Ejecutaríamos: + +```bash +pyenv shell anaconda3-2021.11 +conda info -e +conda create -n py4web_env python=3.7 +conda info -e +# Con esto hemos creado un virtualenv basado en Python 3.7 con conda, podemos activarlo con pyenv +pyenv activate anaconda3-2021.11/envs/py4web_env +``` + + +### pyenv y poetry + +PENDIENTE DE MIRAR + +### pyenv y pip-tools + +PENDIENTE DE MIRAR + +## Instalando paquetes en Python + +__Muy importante leerse el tutorial oficial [Installing Packages](https://packaging.python.org/en/latest/tutorials/installing-packages/)__ + + + +### Instalando un paquete desde el shell de python + +```python + +``` + + + + +## Modulos en Python + +{{< admonition type=info title="Referencias" open=false >}} + +- [Estructura mínima](https://python-packaging.readthedocs.io/en/latest/minimal.html) +- [Estructura tu proyecto](https://docs.python-guide.org/writing/structure/) +- [Paquetes y módulos](https://python-course.eu/python-tutorial/packages.php) +- [Módulos (oficial)](https://docs.python.org/3/tutorial/modules.html) + +{{< /admonition >}} + + +Todos los lenguajes de programación modernos soportan la programación modular. Cualquier programa minimamente complicado hace uso de "módulos" que encapsulan diferentes funcionalidades. + +En Python cualquier fichero con código Python puede considerarse un módulo. Podríamos acceder a cualquier código de cualquier fichero mediante un `import` (hay muchas formas de hacer el `import`) + +Por ejemplo si en un programa hacemos un `import math` podremos acceder a los simbolos (`math.pi`) o funciones (`math.sin(math.pi/2)`) definidos en ese módulo desde nuestro código (usando el prefijo del módulo que es `math.` en este ejemplo) + +### Diferentes tipos de import + +### Packages (Paquetes) + +El siguiente nivel de modularidad en Python es el `package`. Cuando una aplicación o biblioteca se complica y empieza a tener un gran número de módulos podemos organizarlos en paquetes (`package`) + +Basicamente un `package` es un directorio en el que dejamos módulos (ficheros con código Python) pero cumpliendo ciertas condiciones. + +## Exceptions in python + +~~~python + try: + dom_rq = MD.parse(os.path.join(d, 'request_{}.xml'.format(t))) + dom_rs = MD.parse(os.path.join(d, 'response_{}.xml'.format(t))) + except Exception as e: + print('ERROR in some xml file {}'.format(e)) + print(sys.exc_info()[0]) + return None +~~~ + + +## Reverse string in Python + +~~~python +y = x[::-1] +~~~ + +## Minidom + +- Acceder a un atributo +~~~python +req_hotel_avail = dom_rq.getElementsByTagNameNS('*', 'OTA_HotelAvailRQ') + if(req_hotel_avail): + try: + test = req_hotel_avail[0].getAttribute('xmlns') + print(test) + except Exception as e: + print('Error {}'.format(e)) +~~~ + +- Acceder a la lista de atributos + +~~~python +req_hotel_avail = dom_rq.getElementsByTagNameNS('*', 'OTA_HotelAvailRQ') + + if(req_hotel_avail): + try: + test = req_hotel_avail[0].attributes.items() + print(test) + except Exception as e: + print('Error {}'.format(e)) +~~~ + + + +## tkinter + +_tkinter_ (_Tk interface_) es una biblioteca de Python para usar el _Tcl/Tk GUI Toolkit_. Un conjunto de herramientas clásico para desarrollar interfaces gráficos de usuario, y que está disponible en practicamente todas las distribuciones de *nix, incluyendo macOS, también hay disponibles versiones para Windows. + +Es bastante probable que _tkinter_ esté incluido en tu Python de sistema (el python que venga por defecto en tu Linux), o que se haya instalado como dependencia de otros programas de tu Linux lo puedes probar rápidamente sin más que ejecutar desde el terminal: + +```bash +python +>>> import tkinter +>>> tkinter._test() +>>> +``` + +Deberías ver una ventana gráfica con la versión de _Tcl/Tk_ (en mi linux es la 8.6) y un par de botones de demostración (con el botón `Quit` terminas el programa de demo) + + +En el caso de que no esté instalado te fallará el `import`, si lo quieres instalar en el python de sistema en Debian y derivadas el comando debería ser algo parecido al siguiente comando, que es el que hay que ejecutar en Ubuntu y derivadas: + +`sudo apt install python3-tk` + + + +### Aprendiendo _tkinter_ + +Creamos un _virtualenv_: + +```bash +mkvirtualenv tkinter +``` + +Probamos que todo va bien + +```python +#!/usr/bin/env python + +import tkinter +tkinter._test() +``` + +Vamos a echar un ojo a un programa completo + +```python +#!/usr/bin/env python + +# Importamos tkinter entero en nuestro espacio +# Opcionalmente podriamos hacer +# import tkinter as tk +from tkinter import * +from tkinter import ttk # Esta linea importa el submodulo ttk, + # un conjunto de widgets incorporado en 8.5 + + +def calculate(*args): + try: + value = float(feet.get()) + meters.set(int(0.3048 * value * 10000.0 + 0.5) / 10000.0) + except ValueError: + pass + +# Creamos la ventana raiz y le ponemos un título +root = Tk() +root.title("Feet to Meters") + +# Creamos un 'frame' para el contenido, +# esto es importante para usar widgets de un tema +# grid, columnconfigure y rowconfigure determinan +# el comportamiento cuando se cambia el tamaño de +# la pantalla + +mainframe = ttk.Frame(root, padding="3 3 12 12") +mainframe.grid(column=0, row=0, sticky=(N, W, E, S)) +root.columnconfigure(0, weight=1) +root.rowconfigure(0, weight=1) + +# Creamos un widget de tipo 'Entry' que será hijo de 'mainframe' +# o sea está dentro del marco principal + +feet = StringVar() +feet_entry = ttk.Entry(mainframe, width=7, textvariable=feet) +feet_entry.grid(column=2, row=1, sticky=(W, E)) + +meters = StringVar() +ttk.Label(mainframe, textvariable=meters).grid(column=2, row=2, sticky=(W, E)) + +ttk.Button(mainframe, text="Calculate", command=calculate).grid(column=3, + row=3, + sticky=W) + +ttk.Label(mainframe, text="feet").grid(column=3, row=1, sticky=W) +ttk.Label(mainframe, text="is equivalent to").grid(column=1, row=2, sticky=E) +ttk.Label(mainframe, text="meters").grid(column=3, row=2, sticky=W) + +for child in mainframe.winfo_children(): + child.grid_configure(padx=5, pady=5) + +feet_entry.focus() +root.bind("", calculate) + +root.mainloop() +``` + +Lo mismito pero con OOP + +```python +from tkinter import * +from tkinter import ttk + +class FeetToMeters: + + def __init__(self, root): + + root.title("Feet to Meters") + + mainframe = ttk.Frame(root, padding="3 3 12 12") + mainframe.grid(column=0, row=0, sticky=(N, W, E, S)) + root.columnconfigure(0, weight=1) + root.rowconfigure(0, weight=1) + + self.feet = StringVar() + feet_entry = ttk.Entry(mainframe, width=7, textvariable=self.feet) + feet_entry.grid(column=2, row=1, sticky=(W, E)) + self.meters = StringVar() + + ttk.Label(mainframe, textvariable=self.meters).grid(column=2, row=2, sticky=(W, E)) + ttk.Button(mainframe, text="Calculate", command=self.calculate).grid(column=3, row=3, sticky=W) + + ttk.Label(mainframe, text="feet").grid(column=3, row=1, sticky=W) + ttk.Label(mainframe, text="is equivalent to").grid(column=1, row=2, sticky=E) + ttk.Label(mainframe, text="meters").grid(column=3, row=2, sticky=W) + + for child in mainframe.winfo_children(): + child.grid_configure(padx=5, pady=5) + + feet_entry.focus() + root.bind("", self.calculate) + + def calculate(self, *args): + try: + value = float(self.feet.get()) + self.meters.set(int(0.3048 * value * 10000.0 + 0.5)/10000.0) + except ValueError: + pass + +root = Tk() +FeetToMeters(root) +root.mainloop() +``` + + + +### widgets + +* Todos los widgets tienen un padre (excepto la ventana _root_) +* Todos los widgets son de alguna clase + [Widget roundup](https://tkdocs.com/widgets/index.html) +* Las opciones de un widget controlan su comportamiento (incluida su + visualización) Evidentemente dependen también de cual es su clase. + Se intenta que los nombres de los atributos sean consistentes entre + clases. +* Puedes ver toda la configuración de un widget con `button.configure()` +* Puedes cambiar una opción con `button['text']=newvalue` o con + `button.configure(text=newvalue)` + + +Con este programilla podemos ver toda la jerarquía de widgets + +```python +def print_hierarchy(w, depth=0): + print(' '*depth + w.winfo_class() + ' w=' + str(w.winfo_width()) + ' h=' + str(w.winfo_height()) + ' x=' + str(w.winfo_x()) + ' y=' + str(w.winfo_y())) + for i in w.winfo_children(): + print_hierarchy(i, depth+1) +print_hierarchy(root) +``` + +### Gestión de la geometría + +_grid_ es más moderno que _pack_ **[usa _grid_](https://www.geeksforgeeks.org/python-grid-method-in-tkinter/)** + +### Event handling + +* Los eventos son tratados por el _event handling loop_ que no debe ser bloqueado. +* Las cosas interesantes se consiguen asociando _command callbacks_ a eventos + ```python + from tkinter import * + from tkinter import ttk + root = Tk() + l =ttk.Label(root, text="Starting...") + l.grid() + l.bind('', lambda e: l.configure(text='Moved mouse inside')) + l.bind('', lambda e: l.configure(text='Moved mouse outside')) + l.bind('', lambda e: l.configure(text='Clicked left mouse button')) + l.bind('<3>', lambda e: l.configure(text='Clicked right mouse button')) + l.bind('', lambda e: l.configure(text='Double clicked')) + l.bind('', lambda e: l.configure(text='right button drag to %d,%d' % (e.x, e.y))) + root.mainloop() + ``` + +#### Ejemplos + +##### Un campo entrada de texto + +```python +#!/usr/bin/env python + +import tkinter as tk # Lo importamos con un alias para que quede claro que es de tk +from tkinter import ttk # Esta linea importa el submodulo ttk, un conjunto de widgets incorporado en 8.5 +'''Imprime el valor de la entrada de texto. Y lo muestra en una etiqueta''' + + +class App(): + def __init__(self): + self.root = tk.Tk() + self.root.title("Práctica 2.2") + self.root.columnconfigure(0, weight=1) + self.root.rowconfigure(0, weight=1) + + self.frm_root = ttk.Frame(self.root, padding="3 3 12 12") + self.frm_root.grid(column=0, row=0, sticky=(tk.N, tk.W, tk.E, tk.S)) + + self.frm_entry = ttk.Frame(self.frm_root, + borderwidth=2, + relief='sunken', + padding="3 3 12 12") + self.frm_entry.grid(column=0, row=0, sticky=(tk.N, tk.W, tk.E, tk.S)) + + self.var_entry = tk.StringVar() + self.ent_text = ttk.Entry(self.frm_entry, textvariable=self.var_entry) + self.ent_text.grid(column=0, row=0, sticky=(tk.N, tk.W, tk.E, tk.S)) + + self.frm_label = ttk.Frame(self.frm_root, + borderwidth=2, + relief='sunken', + padding="3 3 12 12") + self.frm_label.grid(column=0, row=1, sticky=(tk.N, tk.W, tk.E, tk.S)) + + self.lbl_showEntry = ttk.Label(self.frm_label, + text="No hay valor leido") + self.lbl_showEntry.grid(column=0, + row=0, + sticky=(tk.N, tk.W, tk.E, tk.S)) + + self.frm_button = ttk.Frame(self.frm_root, padding="3 3 12 12") + self.frm_button.grid(column=1, row=0, sticky=(tk.N, tk.W, tk.E, tk.S)) + + self.but_show = ttk.Button(self.frm_button, + text="Print entry", + command=lambda: self.update_print()) + self.but_show.grid(column=0, row=0, sticky=(tk.N, tk.W, tk.E, tk.S)) + + self.root.mainloop() + + def update_print(self): + print(self.var_entry.get()) + self.lbl_showEntry['text'] = f"Valor leido: {self.var_entry.get()}" + + +app = App() +``` + +##### _Checkbuttons_ + +```python +#!/usr/bin/env python + +import tkinter as tk # Lo importamos con un alias para que quede claro que es de tk +from tkinter import ttk # Esta linea importa el submodulo ttk, un conjunto de widgets incorporado en 8.5 +'''Imprime el valor de la entrada de texto. Y lo muestra en una etiqueta''' + + +class App(): + def __init__(self): + self.root = tk.Tk() + self.root.title("Práctica 2.3") + self.root.columnconfigure(0, weight=1) + self.root.rowconfigure(0, weight=1) + + # Creamos el frame ppal en la ventana root + self.frm_root = ttk.Frame(self.root, padding="3 3 12 12") + self.frm_root.grid(column=0, row=0, sticky=(tk.N, tk.W, tk.E, tk.S)) + + # Creamos un frame para los checks dentro del frm_root + self.frm_checks = ttk.Frame(self.frm_root, padding="3 3 12 12") + self.frm_checks.grid(column=0, row=0, sticky=(tk.N, tk.W, tk.E, tk.S)) + + # Creamos cuatro checks con sus variables para almacenar estado + # Les metemos algo de padding para que no queden apelotonados + self.var_check1 = tk.IntVar(value=0) + + self.chk_check1 = ttk.Checkbutton(self.frm_checks, + text='Check 1', + variable=self.var_check1, + padding="3 3 12 12", + command=self.update_chk1) + + self.var_check2 = tk.IntVar(value=0) + self.chk_check2 = ttk.Checkbutton(self.frm_checks, + text='Check 2', + variable=self.var_check2, + padding="3 3 12 12", + command=self.update_chk2) + self.var_check3 = tk.IntVar() + self.chk_check3 = ttk.Checkbutton(self.frm_checks, + text='Check 3', + variable=self.var_check3, + padding="3 3 12 12", + command=self.update_chk3) + self.var_check4 = tk.IntVar() + self.chk_check4 = ttk.Checkbutton(self.frm_checks, + text='Check 4', + variable=self.var_check4, + padding="3 3 12 12", + command=self.update_chk4) + # Colocamos los checks en su frame + self.chk_check1.grid(column=0, row=0) + self.chk_check2.grid(column=0, row=1) + self.chk_check3.grid(column=1, row=0) + self.chk_check4.grid(column=1, row=1) + + # Creamos un frame para el botón + self.frm_button = ttk.Frame(self.frm_root, padding="3 3 12 12") + self.frm_button.grid(column=1, row=0, sticky=(tk.N, tk.W, tk.E, tk.S)) + + # Creamos el boton y lo colocamos + self.btn_readChecks = ttk.Button(self.frm_button, + text="Read checks", + command=lambda: self.update_status()) + self.btn_readChecks.grid(column=0, row=0) + + # Vamos a crear un par de frames más para eventos y para status + # En vez de usar Frame usamos LabelFrame para presumir + + self.frm_events = ttk.LabelFrame(self.frm_root, + padding="3 3 12 12", + text='Events') + self.frm_status = ttk.LabelFrame(self.frm_root, + padding="3 3 12 12", + text='Status') + + # Y los colocamos en su sitio + self.frm_events.grid(column=0, row=1) + self.frm_status.grid(column=1, row=1) + + # Y por ultimo los mensajes de eventos y de status + self.lbl_events = ttk.Label(self.frm_events, text="Sin eventos") + self.lbl_status = ttk.Label(self.frm_status, text="Sin status") + + # Los colocamos como siempre + self.lbl_events.grid(column=0, row=0) + self.lbl_status.grid(column=0, row=0) + + # Last but not less lanzamos el bucle para atender a todos los eventos + self.root.mainloop() + + # Lo que sigue son los métodos que actualizan las etiquetas de eventos + # y status, lo de tener cuatro métodos iguales queda feo, pero no da tiempo a mas + def update_chk1(self): + if (self.var_check1.get()): + self.lbl_events['text'] = 'Set check1' + else: + self.lbl_events['text'] = 'Unset check1' + + def update_chk2(self): + if (self.var_check2.get()): + self.lbl_events['text'] = 'Set check2' + else: + self.lbl_events['text'] = 'Unset check2' + + def update_chk3(self): + if (self.var_check3.get()): + self.lbl_events['text'] = 'Set check3' + else: + self.lbl_events['text'] = 'Unset check3' + + def update_chk4(self): + if (self.var_check4.get()): + self.lbl_events['text'] = 'Set check4' + else: + self.lbl_events['text'] = 'Unset check4' + + def update_status(self): + self.lbl_status[ + 'text'] = f"Check1 = {self.var_check1.get()} Check2 = {self.var_check2.get()} \ +Check3 = {self.var_check3.get()} Check4 = {self.var_check4.get()}" + + +app = App() + +``` + +## Estudio + +* [Análisis de un ransomware](https://www.welivesecurity.com/la-es/2020/07/29/analisis-codigo-fuente-ransomware-escrito-python/) +* [Pomodoro en python](https://medium.com/@fidel.esquivelestay/build-a-pomodoro-timer-using-python-d52509730f60) +* [Tkinter en RealPython](https://realpython.com/python-gui-tkinter/) +* [Another Tkinter tutorial](https://tkdocs.com/tutorial/index.html) +* [Tercer tutorial para Tkinter](https://www.geeksforgeeks.org/python-tkinter-tutorial/) + +## Create http server + +Comparte los ficheros de la carpeta + +```python +import os +from http.server import HTTPServer, CGIHTTPRequestHandler +# Make sure the server is created at current directory +os.chdir('.') +# Create server object listening the port 80 +server_object = HTTPServer(server_address=('', 80), RequestHandlerClass=CGIHTTPRequestHandler) +# Start the web server +server_object.serve_forever() +``` + +## Decorators + +* [Codeship: Python decorators](https://www.thecodeship.com/patterns/guide-to-python-function-decorators/) +* [Python Decorators Explained](https://jeffknupp.com/blog/2013/11/29/improve-your-python-decorators-explained//) + + +## Static method vs Classmethod + +* [Static methods](https://stackoverflow.com/questions/136097/difference-between-staticmethod-and-classmethod#1669524) +* [More on static methods](https://stackabuse.com/pythons-classmethod-and-staticmethod-explained/) + +## Referencias + +* [Python3 OOP (realpython)](https://realpython.com/python3-object-oriented-programming/) + + + +## Curso de Python + +### Info do curso + +Sabados 11.00 - 13.00 horas + +- @caligari (é mais de práctica) +- @salvari (é mais de teoría) + +Estructura do curso + +- Teoría e práctica +- Práctica "de deberes" +- Preguntas dúbidas e tertulia + +Dinámica do curso: + +- Non se graba +- Non hai transparencias, o profe teórico é un vago (tamén intenta ser presumido e impaciente) +- [Apuntes colaborativos](https://pad.disroot.org/p/cafe-con-gotas-de-python) +- Iremos incorporando ao libro exemplos e notas que vexamos útiles + + + +### Bricolabs + +[Bricolabs](https://bricolabs.cc/) + +### Documentación do curso + +- Gitlab de Ekaitz: https://gitlab.com/ElenQ/Publishing/python/-/tree/master/es +- Tradución ao galego (coordinador: Rafa Gaioso [Melisa](https://www.melisa.gal/)) +- https://pad.disroot.org/p/curso_python_memoria.md + + +### Outros enlaces de interese + +* Itinerario formativo con ou sen mentorización: https://exercism.io/tracks/python + + +## Apuntes sesión 01 + + +Algo de Historia + +Fai 30 anos... a explosión dos intérpretes: + +Perl +: Larry Wall (1987) +: Killer app: +: - CGI perlcgi integrado no código de Apache. +: - Linguaxe pegamento para sysadmins +: - Perl Template Toolkit + +Python +: Guido Van Rosum (1991) Benevolent Dictator for Life + (https://en.wikipedia.org/wiki/Benevolent_dictator_for_life), un + título moi usado no mundo hacker e inventado para Guido +: Killer app: +: - Zope, Plone, Django, Calibre, Web2py +: - Integración con Gimp, con KiCAD, con FreeCAD, con .... +: - Big Data, iPython (agora Jupyter), Machine Learning, Deep Learning + +PHP +: Rasmus Lerdorf (1995) +: Killer app: +: - Wordpress +: - Milleiros de aplicacións web + +Ruby +: Matz Matsumoto (1995) +: Killer app: +: - Ruby on rails +: Pouco coñecido en occidente pero super implantado en Asia + +## Las tres virtudes del programador + +(según Larry Wall, creador do Perl) + +Pereza +: _"La virtud que te hace esforzarte para reducir tu gasto global de + energía. Hace que escribas programas que te ahorran trabajo y te + hace documentar esos programas para que no tener que contestar + preguntas acerca de como funcionan."_ + +Impaciencia +: _"El ansia que sientes cuando el ordenador remolonea. Esto hace que + escribas programas que no sólo satisfacen tus necesidades si no que + se anticipan a ellas. O al menos lo intentan."_ + +Orgullo +: _"La virtud que te hace escribir (y mantener) programas intachables, + de los que el resto de la gente no pueda hablar mal."_ + +O lema de Perl: ___TIMTOWTDI "There is more than one way to do it"___, +ou sexa, hai moitas formas de facelo (e ademais eso mola moito) + +A resposta de Python (e decir, de Guido, que é colega de Larry) + +___"There should be one — and preferably only one — obvious way to do +it."___ Normalmente hai unha forma obvia e óptima de facelo (é iso +mola moito mais de cara a manter o software) + +Qué é python? + +* Linguaxe de programación +* Interpretado non compilado + +Pros + +* Simplicidade +* Versatilidade facilidade de uso e desenrolo rápido +* Software libre e aberto, cunha comunidade moi activa +* Un conxunto de bibliotecas enorme e super completo +* Excelente para prototipos (código compacto) + +Limitacións o Desventaxas do Python + +* Limitacións de velocidade (é interpretado e iso sempre penaliza a + velocidade) +* Problemas có Threading (estou empezando a programar, non me importa) +* Non ten implantación destacable en dispositivos móbiles: Android e + IOS (pero mira Kivy se estás moi interesado neste campo) + +Pingas de Python: + +* Terminal vitaminado para python: `bpython3` ou `ptpython` +* Axuda interactiva: `help('string')` +* Saber ruta do intérprete en linux: `which python` ou `which python3` +* Script, empeza coa línea _shebang_, para decirlle que intérprete usar + ```bash + #!/ruta al intérprete + ``` + Ou tamén + ```bash + #!/usr/bin/env python3 + ``` +* Ver "posición da memoria" (en realidade `id` devolve un identificativo único) da variable + ```python + hex(id(a)) + ``` +* saber o tipo dunha variable: `type(variable)` + +Mutabilidad: + +* A tupla e inmutable +* A lista e mutable + + +### Estilo de programación + +* Readability counts (the zen of python) +* El código se lee mucho más a menudo de lo que se escribe +* [Reglas de estilo pep8](https://www.python.org/dev/peps/pep-0008/) +* [Reglas de docstring (los comentarios de documentación) pep257](https://www.python.org/dev/peps/pep-0257/) + + +### Tipos de datos + +```text +TIPOS + | + |- Numéricos + | |- Enteros (int) + | |- Coma flotante (float) + | |- Complejos + | + |- Texto + | + |- Booleanos + |- True (Verdadero) + |- Falso (Falso) +``` + +### Operadores + +```text +OPERADORES + | + |- Aritméticos + | |- Suma + + | |- Resta - + | |- Multiplicación * + | |- División / + | |- División entera // + | |- Módulo % + | |- Exponente ** + | + |- Comparación + | |- Igual que == + | |- Diferente que != + | |- Mayor que > + | |- Menor que < + | |- Mayor o igual que => + | |- Menor o igual que <= + | + |- Lógicos + | |- AND AND + | |- OR OR + | |- NOT NOT + | + |- Asignación + | |- Igual = + | |- Incremento += + | |- ? *= + | |- ? /= + | |- ? %= + | |- ? **= + | |- ? //= + | + |- Especiales + |- IS IS + |- IS NOT IS NOT + |- IN IN + |- NOT IN NOT IN + +``` + +## Apuntes sesión 02 + +### Bucle for + +El bucle `for` clásico se puede conseguir facilmente con la función `range()` + +```python +for i in range(10): + print(i) +``` + +En los bucles `for` y `while` se pueden usar las palabras clave `continue`, `break` y `else`: + +```python +for i in (iterable): + . + . + . + continue # salta inmediatamente al siguiente bucle for + . + . + break # interrumpe el bucle for y sale de el + else: + # Este bloque de ordenes se ejecuta si el for + # se completa sin ejecutar el break +``` + +--- + +__OJO__ +El `else` para bucles es una de las características más controvertidas +de Python, el propio Guido dijo que nunca debió incluirla en la +especificación del lenguaje, o al menos que se debió usar otra palabra +que no fuera `else` + +--- + + +Para un `while` sería lo mismo + +```python +while(condicion): + . + . + . + continue # salta al comienzo del siguiente bucle + . + . + break # interrumpe el while y sale del bucle + else: + # Este bloque se ejecuta siempre que + # la condición del while sea falsa + # y no salgamos del mismo con break +``` + +Las sentencias de tratamiento de excepciones también tiene `else` y +además una clausula `finally` + +```python +try: + x = 1 +except: + print('Hay un erro, no se puede asignar x') +else: + print('No hay excepciones') +finally: + print('Siempre imprimimos esto') +``` + + +### Apuntes sesión 03 + +Sábado, 18/04/2020 +Exercism + +Neste enlace tedes a páxina de exercism, onde vos podedes dar de alta +(opcional) para facer o track de Python. + +https://exercism.io/tracks/python/exercises + +Se vos dades de alta en exercism, veredes que hai instruccións para +instalar un cliente de linea (de linea de comandos) que permite +descargar os exercicios e subir as solucións. E moi doado de instalar +non vos preocupedes. (Guía de instalación) + +Lembrade traballar en un entorno virtual, se queredes instalar o +módulo pytest que suxiren en exercism, facedeo dentro do voso +virtualenv, poderíades facer algo como: + +```bash +mkdir curso +cd curso +python3 -m venv .venv +source .venv/bin/activate +pip install pytest +``` + +Lembrade o comentado no curso, agora estamos escribindo "Baby Python", +non vos preocupedes do estilo ou se o facedes ben ou mal, o importante +é que funcione. + +O voso python mellorará coa práctica e lendo código de outros. + +Lembrade tamén que haberá bibliotecas para facer todo tipo de cousas, +é imposible coñecelas todas. Có tempo coñeceredes as que usedes mais +frecuentemente, pero non serán moitas. Sempre hai que apoiarse na +documentación a través de internet. + + + +Solución exercicio 1 (Adiviña o número) + +```python +#!/usr/bin/env python + +from random import randint + +sigue_xogando = True + +def es_valida(entrada): + + try: + x = int(entrada) + except ValueError as e: + print('Tes que introducir un número enteiro entre o 1 e o 100') + return False + except Exception as e: + print('Erro descoñecido, contacta co autor do programa') + return False + + if (x > 100 or x < 1): + print('Error, o número está fora do rango de 1 a 100') + return False + + return True + +# Aquí empeza o programa principal + + +while sigue_xogando: + mi_numero = randint(1, 100) + intentos = 0 + + finalizado = False + while not finalizado: + suposicion = input('Dime un número: ') + if (es_valida(suposicion)): + intentos += 1 + if (mi_numero > int(suposicion)): + print('O meu número e maior, Proba outra vez') + elif (mi_numero < int(suposicion)): + print('O meu número e menor. Proba outra vez') + else: + print('Acertaches!. O meu número é: ' + str(mi_numero)) + print('E só necesitaches {} intentos'.format(intentos)) + finalizado = True + + outra_vez = input('Queres xogar outra vez) [s/n]') + if (outra_vez in ['N', 'n']): + sigue_xogando = False +``` + + +Solución exercicio 2: Simular tiradas de dados + + +```python +#!/usr/bin/env python + +from sys import (argv, exit) +from random import randint + +max_stars = 40 + +usage = '''Uso: +dados.py numtiradas [dados] + +- numtiradas Parámetro obligatorio + É o número de tiradas a simular, ten que ser un enteiro positivo. + +- dados Parámetro opcional + E o número de dados que imos usar, ten que ser un enteiro maior que 2 +''' + + +def check_parameters(): + dados = 2 + + if (not (len(argv) in [2, 3])): + raise SystemExit(usage) + + try: + tiradas = int(argv[1]) + if (len(argv) == 3): + dados = int(argv[2]) + except ValueError as e: + raise SystemExit(usage) + except Exception as e: + print(type(e)) + raise SystemExit(usage) + + return (tiradas, dados) + + +# -------------------- main -------------------- + +tiradas, dados = check_parameters() + +# print('Simulando {} tiradas de {} dados'.format(tiradas, dados)) +print(f'Simulando {tiradas} tiradas de {dados} dados') + + +results = {i: 0 for i in range(dados, (6 * dados) + 1)} + +for i in range(tiradas): + result = 0 + for i in range(dados): + result += randint(1, 6) + results[result] += 1 + +padd = len(str(max(results.values()))) +for i in sorted(results): + print( + f'{i:02}: [{str(results[i]).zfill(padd)}] {"*" * int(results[i] * max_stars / max(results.values()))}') + +``` + +## Apuntes sesión 04 + +Módulo +: Escritos normalmente para ser importados + +Script +: Escritos normalmente para ser ejecutados desde la linea de comandos + +Programa +: Seguramente compuesto por varios módulos + + +### Importar módulos + +```python +import M # Hay que usar M.func para acceder a una f importada +from M import f # No tenemos que calificar f para usarla +from M import (f, g, h) # Podemos importar varios objetos del módulo al mismo tiempo +``` + +### Scopes (LEGB) + +Estrictamente hablando Python recorre los siguientes `scopes` cuando busca un símbolo + +Local +: Nombres asignados dentro de una función (también aplica a lambdas) y que no se preceden con la palabra clave global + +Enclosing-function +: Nombres asignados dentro de cualquier función padre en una cadena de funciones anidadas. De dentro hacia fuera e incluye lambdas + +Global (a nivel de módulo) +: Nombres asignados en el nivel superior de un modulo (fichero) de python. O asignados dentro de una función pero precedidos de la palabra clave global + +Built-in (Python) +: Nombre preasignados en el built-in por el intérprete de Python. P.ej: open, range, SyntaxError, etc + + +`global` y `nonlocal` + +Algunos namespaces: + +* Global +* Módulos +* Clases +* Generadores +* Función + + +Utilizar `.get` para crear un contador cuando no has definido el número inicial del parámetro que quieres contar: + +`.get(loquesea, 0)` : Devuelve lo que sea si existe, y si no, devuelve 0. + +__palabrainterna__ + +## Apuntes sesión 05 + + +### Flujo de programa + + +### lambdas + +Son funciones "telegráficas" que se usan a veces para acortar el +código. + +En general las lambdas deberían ser de un solo uso, y en casos muy +particulares donde necesitamos una función muy simple (casí siempre +como parámetro para otra función o una _list comprehension_ + +Si te encuentras peleando para implementar una lambda compleja, es +casi seguro que deberías dejarlo y emplear una función normal. + + +### Parámetros de funciones + +Tenemos que distinguir entre dos escenarios: antes de Python 3.8 y +después de Python 3.8. + +Hasta ahora teníamos: + +Aquí tenemos una funcíon definida con tres parámetros, uno de ellos +(c) con un valor por defecto + +Al llamar la función c es opcional puesto que tiene valor por defecto + +Cualquier parámetro lo podemos pasar por posición, pero todos los +posicionales tienen que ir desde el principio. En cuanto pasemos un +parámetro por nombre ya no podemos añadir ninguno posicional. + +Cualquier parámetro lo podemos pasar también por nombre, siempre +dejando los posicionales (si los hay) al principio en su lugar. En +cuanto pasemos un parámetro por nombre ya no podemos añadir ninguno +posicional. + +```python +def fn (a, b, c = 1): # a y b obligatorios, c opcional ya que + return a * b + c # tiene valor por defecto. + +print(fn(1, 2)) # devuelve 3, a y b son posicionales +print(fn(1, 2, 3)) # devuelve 5, a, b y c son posicionales +print(fn(c = 5, b = 2, a = 2)) # devuelve 9, pasamos todos "con nombre" +print(fn(b = 2, a = 2)) # devuelve 5, pasamos a y b con nombre +print(fn(5, c = 2, b = 1)) # devuelve 7, a es posicional, b y c con + # nombre +print(fn(8, b = 0)) # devuelve 1, a es posicional, + # b lo pasamos con nombre y c toma el valor + # por defecto +``` + +Si definimos la función como + +```python +def fn(a, b, c = 0, *args): +``` + +Sigue en pie la regla de que TODOS los parámetros posicionales tienen +que ir al principio. Podemos pasar todos los parámetros posicionales +que queramos, los tres primeros se asignan a: a, b y c respectivamente +el resto se capturan en la tupla `args`. Por convención se emplea la +tupla llamada `args`. Podríamos emplear cualquier otro nombre para la +tupla, pero dificultaríamos la inteligibilidad del código por parte de +otros programadores. + +```python +def fn(a, b, **kargs): +``` + +Esta función admite dos párametros posicionales, el resto de parámetros adicionales tienen que ser con nombre y quedan capturados en el diccionario `kargs` + +```python +def fn(a, b, *args, **kargs) +``` + +Esta función admite dos o mas parámetros posicionales, los dos +primeros se asignan a `a` y `b`, el resto se capturan en la tupla +`args`. También podemos meter todos los parámetros que queramos con +nombre, se capturan en el diccionario `kargs` + + +Desde Python versión 3.8 además tenemos dos separadores de paramétros +`/` y `*` + +En el caso de que usemos los separadores: + +* A la izquierda de `/` son parámetros posicionales puros, no se puede llamarlos por nombre +* A la derecha de `*` son parámetros por nombre obligatoriamente +* Lo que va en el medio de los dos simbolos, pueden ser usados posicionalmente o por nombre + +```python +def f(a=2, /) # Parámetro posicional opcional + # +`def f(a, /)` # Parámetro posicional, obligatorio + # +def f(*, a=1) # Parámetro por nombre, opcional + +def f(*, a) # Parámetro por nombre, obligatorio + +def f(a=1) # Podemos pasar el parámetro por posición o por nombre y + # además es opcional + +def f(a) # Podemos pasar el parámetro por posición o por nombre y + # es obligatorio +``` + +--- + +__OJO: ¡No usar parámetros mutables en la definicion de las funciones!__ + +--- + +Algunos ejemplos de paso de parámetros con valor por defecto: + +Con valor por defecto inmutable. No tiene problemas. + +```python +def banner(message, border='-') + line = border * len(message) + print(line) + print(message) + print(line) +``` + +Con un valor que varía a lo largo de la ejecución del programa +(problema: no va a variar) + +Pasamos como parámetro por defecto la hora actual, pero la llamada a +ctime solo se ejecuta una vez al inicio del intérprete de python, no +cada vez que llamamos a la función + +~~~~python +import time + +def show_default(arg=time.ctime()) + print(arg) +~~~~ + +Pasamos como parámetro un valor mutable (muy mala idea, no lo hagais) + +Pasamos como valor por defecto la lista vacía, en la práctica pasar un +valor por defecto equivale a crear un closure, la función mantiene su +contexto a lo largo del ciclo de vida del programa con una referencia +al valor por defecto del parámetro alist (que es una lista y por tanto +mutable) + +Cada vez que llamamos a la función sin parámetros la función añade una +nueva cadena 'uno' a la lista almacenada en el closure como "valor por +defecto de alist" + +Si pasamos un parámetro, la cosa cambia, python crea una nueva lista +en memoria y asigna la referencia a esa lista como parámetro de la +función, con lo cual se ejecuta como esperamos. Al terminar la +referencia a esa lista que pasámos como parámetro no se almacena en el +closure de la función y si no hay otras referencias a ella en el +programa será reclamada por el garbage colector. + +~~~~python +def return_list(alist=[]): + alist.append('uno') + return(alist) +~~~~ + +Solución al problema anterior (probada) + +~~~~python +def return_list(alist=None): + if alist is None: + alist = [] + alist.append('uno') + return(alist) +~~~~ + +### Funciones pasadas como parámetros. Composición de funciones: + +~~~~python +def is_even(value): +"""Return True if *value* is even.""" + return (value % 2) == 0 + +def count_occurrences(target_list, predicate): + """Return the number of times applying the callable *predicate* to a + list element returns True.""" + return sum([1 for e in target_list if predicate(e)]) + +my_predicate = is_even +my_list = [2, 4, 6, 7, 9, 11] +result = count_occurrences(my_list, my_predicate) +print(result) +~~~~ + +### Funciones anidadas: + +```python +def parent(): + print("Printing from the parent() function") + a = 'uno' + def first_child(): + a = 'dos' + print("Printing from the first_child() function") + + def second_child(): + print("Printing from the second_child() function") + b = 'dos' + + print(b) + second_child() + first_child() +``` + +### Funciones anidadas y paso de funciones como parámetro + +~~~~python +def surround_with(surrounding): + """Return a function that takes a single argument and.""" + def surround_with_value(word): + return '{}{}{}'.format(surrounding, word, surrounding) + return surround_with_value + +def transform_words(content, targets, transform): + """Return a string based on *content* but with each occurrence + of words in *targets* replaced with + the result of applying *transform* to it.""" + result = '' + for word in content.split(): + if word in targets: + result += ' {}'.format(transform(word)) + else: + result += ' {}'.format(word) + return result + +markdown_string = 'My name is Jeff Knupp and I like Python but I do not own a Python' +markdown_string_italicized = transform_words(markdown_string, + ['Python', 'Jeff'], + surround_with('*')) +print(markdown_string_italicized) +~~~~ + + +### Closures (o cierres) + +Los closures son funciones que mantienen un "entorno" en el que +"recuerdan" variables. En el ejemplo de abajo + +~~~python +def create_incrementer_function(increment): + def incrementer (val): + # Recuerda que esta función puede ver el valor `increment` por + # por haber nacido en un contexto superior (en su func. padre) + return val + increment + return incrementer +increment5 = create_incrementer(5) +print(increment5(3)) # Imprimirá 8 por la pantalla +~~~ + +### Decoradores + +~~~python +def my_decorator(func): + def wrapper(*args, **kargs): + print("Something is happening before the function is called.") + func(*args, **kargs) + print("Something is happening after the function is called.") + return wrapper +~~~ + +## OOP - Programación orientada a objetos + +La programación orientada a objetos se basa en cuatro principios básicos + +* Encapsulado +* Abstracción +* Herencia +* Polimorfismo + + +Código de la sesión 06: + +~~~~python +'''OOP sample code''' +class Flight: + '''A flight with a particular aircraft''' + + def __init__(self, number, aircraft): + if not number[:2].isalpha(): + raise ValueError(f'No airline code in {number}') + if not number[:2].isupper(): + raise ValueError(f'Invalid airline code in {number}') + if not (number[2:].isdigit() and int(number[2:]) <= 9999): + raise ValueError(f'Invalid route number in {number}') + self._number = number + self._aircraft = aircraft + rows, seats = self._aircraft.seating_plan() + self._seating = [None] \ + + [{letter: None for letter in seats} for _ in rows] + def number(self): + return(self._number) + def airline(self): + return self._number[:2] + def aircraft_model(self): + return self._aircraft.model() + def allocate_seat(self, seat, passenger): + '''Allocates a seat for a passenger + Args: + seat: A seat designator such as '12C' or '21F' + passenger: The passenger name + Raises ValueError if seat is unavailable + ''' + rows, seat_letters = self._aircraft.seating_plan() + letter = seat[-1] + if letter not in seat_letters: + raise ValueError(f'Invalid seat letter: {letter}') + row_text = seat[:-1] + try: + row = int(row_text) + except ValueError: + raise ValueError(f'Invalid seat row: {row_text}') + if row not in rows: + raise ValueError(f'Invalid row number: {row}') + if self._seating[row][letter] is not None: + raise ValueError(f'Seat {seat} already occupied') + self._seating[row][letter] = passenger +class Aircraft: + def __init__(self, registration, model, num_rows, num_seats_per_row): + """Models an aircraft + """ + self._registration = registration + self._model = model + self._num_rows = num_rows + self._num_seats_per_row = num_seats_per_row + def registration(self): + '''Returns aircraft registration code + ''' + return self._registration + def model(self): + '''Returns aircraft model + ''' + return self._model + def seating_plan(self): + '''Returns seating plan for aircraft, as a tuple of rows numbers + (range) and seats letters (list)''' + return (range(1, self._num_rows + 1), + "ABCDEFGHJK"[:self._num_seats_per_row]) +~~~~ + +## Docker y Python + +Podemos usar Docker para tener proyecto aislados en sus contenedores. Esto nos puede simplificar la vida a la hora de gestionar dependencias, versiones de Python o distintas configuraciones. + + + +### Containers + +Vamos a crear un contenedor con una aplicación muy chorras. Para crear nuestro contenedor suponemos que creamos el directorio `app` en alguna parte de nuestro sistema de ficheros. + +```bash +mkdir -p app/src +touch app/{requirements.txt,dockerfile} +touch app/src/server.py +tree app +app +├── dockerfile +├── requirements.txt +└── src + └── server.py +``` + +El contenido del fichero `server.py` tendría esta pinta: + +```python3 +from flask import Flask +server = Flask(__name__) + +@server.route("/") + def hello(): + return "Hello World!" + +if __name__ == "__main__": + server.run(host='0.0.0.0') +``` + +Como requisito necesitamos tener `Flask` instalado así que en `requirements.txt` tedremos + +```ini +Flask==2.1.0 +``` + +Ahora vamos a definir como se construye la imagen Docker con nuestro `dockerfile`: + +```dockerfile +# set base image (host OS) +FROM python:3.10.9 + +# set the working directory in the container +WORKDIR /code + +# copy the dependencies file to the working directory +COPY requirements.txt . + +# install dependencies +RUN pip install -r requirements.txt + +# copy the content of the local src directory to the working directory +COPY src/ . + +# command to run on container start +CMD [ "python", "./server.py" ] +``` + +Nuestro `dockerfile`: +- parte de la imagen oficial para Python 3.10.9 +- define el directorio de trabajo `/code` (en el contenedor) +- copia el fichero `requirements.txt` desde el host (nuestro ordenador) al directorio de trabajo del contenedor +- copia el directorio `src/` al directorio de trabajo del contenedor +- define el comando de arranque de los contenedores basados en esta imagen como `python ./server.py` + +Ya podemos construir nuestra imagen con el comando: `docker build -t myimage .` Y una vez construida (ver con `docker images`) podemos crear contenedores a partir de esta imagen: + +```bash +# Launch container mapping port 5000 in host +docker run -d -p 5000:5000 myimage + +# Launch container and open terminal +docker run -it myimage /bin/bash +``` + +### Optimizando la construcción de imágenes Docker + +__Imagen Base__ + +: Por supuesto conviene usar __siempre__ imágenes oficiales + +: La imagen que escojamos como base determina muchas de las propiedades de la imagen final. Generalmente hay opciones para construir imágenes ligeras (casi siempre basadas en _Alpine Linux_) Para Python podemos buscar las imágenes oficiales de tipo ___slim___, por ejemplo `python:3.10.9-slim` + +__Orden de instrucciones en el _dockerfile___ + +: Para acelerar las operaciones de recontrucción de imágenes, que serán muy frecuentes durante el desarrollo, Docker implementa mecanismos de caché. Para aprovechar esta funcionalidad es muy importante que dejemos las opciones más estables (que tengan menos cambios a lo largo del desarrollo) al principio del _dockerfile_. Por ejemplo nuestro _dockerfile_ de ejemplo pone al principio los comandos de instalación de dependencias y despues copia los fuentes. Como las dependencias no cambiarán tan frecuentemente como el código fuente de nuestro proyecto se podran aprovechar las imágenes de la caché. + +__Construcción de imágenes por etapas__ + +: Esta técnica se usa más bien para preparar las imágenes para distribución, no en la fase de desarrollo. Por ejemplo veamos como contruir nuestra aplicación y preparar una imagen ligera para distribución: + +```dockerfile +# first stage +FROM python:3.10.9 AS builder +COPY requirements.txt . + +# install dependencies to the local user directory (eg. /root/.local) +RUN pip install --user -r requirements.txt + +# second unnamed stage +FROM python:3.10.9-slim +WORKDIR /code + +# copy only the dependencies installation from the 1st stage image +COPY --from=builder /root/.local /root/.local +COPY ./src . + +# update PATH environment variable +ENV PATH=/root/.local:$PATH + +CMD [ "python", "./server.py" ] + +``` + +{{< admonition type=note title="TODO" open=true >}} + +Comprobar estas referencias y completar + +- +- +- +- + +{{< /admonition >}} + + +## Varios experimentos con interfaces de usuario + +### Tkinter + +### PySimpleGUI + +### NiceGUI + +### Textual + + + +## Miscelanea + + +### Soluciones de exercism + +Pistas para el ejercicio 'matrix' + +Una matriz o array en python se puede representar como una 'lista de listas' + +Asi la matriz + +1 2 3 +4 5 6 +7 8 9 + +Podría representarse como una lista de python que almacenase una lista por cada fila + +~~~python +my_array = [ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9] + ] +~~~ + +El método clásico de transponer (que las filas se conviertan en columnas) una matriz en python era: + +~~~python +my_array_in_rows = [ + [1, 2], + [3, 4], + [5, 6] + ] +my_array_in_cols = list(map(list, zip(*my_array_in_rows))) +~~~ + +Que no es muy intuitivo ¬_¬ + + +Pero en python moderno con las list comprehension podemos escribir: + +~~~python +elem_by_row = len(array_in_rows[0]) +my_array_in_cols = [[linea[i] for linea in my_array_in_rows] for i in range(elem_by_row)] +~~~ + +Hemos supuesto que el array está "bien formado", es decir que todas +las filas tienen el mismo número de elementos, asi que para calcular +cuantos elementos hay por fila nos vale la logitud de cualquier fila +(lo calculamos con la fila cero) + +La 'comprehension' externa recorre los índices de los elementos de una +fila (serían los números de columna) y para cada número de columna +devuelve otra 'comprehension' (que llamaremos interna) + +La 'comprehension' interna devuelve una lista formada por el elemento +i-ésimo de cada linea del array, (es decir, los elementos de la +columna i-ésima. + +en nuestro ejemplo el contenido de my_array_in_cols, al final sería + +```text +[ + [1, 3, 5], + [2, 4, 6] +] +``` + +### Jupyter (antes conocido como iPython) + + +Por descontado, hay que instalarlo en un _virtualenv_ + +~~~bash +python3 -m venv venv-jupyter +pip install jupyter +~~~ + +En la sesión del curso instalamos tambien seaborn para ver gráficos: +~~~bash +# Asegurate de tener activo el virtualenv antes de instalar +pip install seaborn +pip install statsmodels +~~~ + + +### Expresiones Regulares en Python + +Un par de páginas web (hay muchísimas) para probar nuestras expresiones regulares + +* +* + +Un buen recurso para aprender expresiones regulares con varias +versiones, entre ellas castellano + +* + + +Un muy buen tutorial de expresiones regulares en python: + +* Parte I +* Parte II + + +### Algunas librerias y frameworks interesantes + + +#### argparse + +~~~python +# -------------------- main -------------------- +# Parse command line options +arg_parser = argparse.ArgumentParser( + description='Process files from input dir. Writes results in output dir') +arg_parser.add_argument( + '-i', '--idir', help='Input directory', required=True) +arg_parser.add_argument( + '-o', '--odir', help='Output directory', required=True) + +args = arg_parser.parse_args() + +# Check directories +if not (os.path.isdir(args.idir)): + print('Input dir <{}> is not a valid directory'.format(args.idir)) + exit() + +if not (os.path.isdir(args.odir)): + print('Output dir <{}> is not a valid directory'.format(args.odir)) + exit() +~~~ + + +* Toga para hacer GUI (interfaces de usuario) +* Kivy Interfaces de usuario, dispositivos móviles +* Dash Para hacer cuadros de mandos, se + basa en plotly que a su vez se basa en d3.js. Pese a la apariencia + de la página web, es software libre con licencia MIT + + +#### Frameworks Web + +__Flask__ + +El más simple de los frameworks, siempre es bueno dedicarle algún +tiempo para ver los conceptos básicos. Se puede hacer cualquier +aplicación web con Flask, pero los frameworks más potentes nos +ahorrarán mucho trabajo. + +Sigue siendo imbatible para aplicaciones sencillas + +Un buen tutorial de Flask: [El mega tutorial de Flask](https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world). Son 23 capitulos publicados en la web. + + +__web2py__ + +Un framework más potente que Flask y más sencillo que Django + +[El libro oficial de web2py](http://web2py.com/book) (hay versión en español) + +El capítulo 3 del libro es un buen punto de partida para autoformación + +Un buen tutorial de web2py, son siete videos de unos 20 minutos cada uno: + +* [Server Side programming with web2py](https://www.youtube.com/watch?v=zmSOnKkm8Y0&list=PL5E2E223FE3777851) + +* [Los videos de Maximo di Pierro](https://vimeo.com/user315328) (el creador de web2py) un curso de 30 videos super-completo + Aseguraos de empezar por el primer video (hay que ir hacia atrás en el historial del usuario) El primero sería [este](https://vimeo.com/104801796) + +* Una guia (sin terminar) que puede valer para instalar web2py es [esta](https://gitlab.com/salvari_guides/web2py) + +#### celery + +{{< admonition type=info title="Referencias" open=true >}} + +- [Documentación Oficial](https://docs.celeryq.dev/en/stable/index.html) +- [Demonizar Celery](https://docs.celeryq.dev/en/stable/userguide/daemonizing.html#daemonizing) +- [Celery Python Guide: Basic, Examples and Useful Tips](https://codeburst.io/the-celery-python-guide-basics-examples-and-useful-tips-d8da1fcfaea3) +- [Asyncronous Task in Python with Celery](https://medium.com/analytics-vidhya/asynchronous-tasks-in-python-with-celery-e6a9d7e3b33d) +- [Full Stack Python Celery Collection](https://www.fullstackpython.com/celery.html) + +{{< /admonition >}} + +Una *cola de tareas* para Python. + +Una _cola de tareas_ sirve para distribuir el trabajo en _threads_ (hilos de ejecución concurrente) o entre distintas máquinas. + +Las entradas para la _cola de tareas_ son unidades de trabajo denominadas tareas (_tasks_). Trabajadores (_workers_) dedicados monitorizan la cola constantemente y ejecutan las tareas encoladas. + +_Celery_ se comunica mediante mensajes, normalmente usando un _broker_ que hace de mediador entre clientes y _workers_. Los clientes (_clients_) ofertan tareas enviando mensajes a la cola. El _broker_ facilita que esos mensajes lleguen a los _workers_. + +Así pues un sistema _Celery_ se compone de múltiples _workers_ y _brokers_, lo que permite obtener __alta disponibilidad__ y sistemas fáciles de __escalar horizontalmente__. + +_Celery_ está escrito en Python pero se ha hecho tan popular que se puede usar desde varios lenguajes de programación. + +_Celery_ __necesita__ un _broker_ para funcionar, tanto _RabittMQ_ como _Redis_ ofrecen soluciones completamente funcionales pero _Celery_ soporta muchos más. + +Adicionalmente podemos instalar un sistema que actúe como _almacén de resultados_ (_results store_). _Celery_ soporta muchos diferentes. + +##### Montando un escenario sencillo con Celery + +Lanzamos un _RabittMQ_ dockerizado: + +```bash +docker run -d -p 5672:5672 rabbitmq +``` + +Creamos nuestro entorno virtual como de costumbre e instalamos _Celery_ + +```bash +cd +pyenv virtualenv 3.9.7 ve_celery +pyenv local ve_celery +myve +pip install celery +``` + +Creamos un fichero `tasks.py`: + +```python3 +from celery import Celery + +app = Celery('tasks', broker='pyamqp://guest@localhost//') + +@app.task +def add(x, y): + return x + y +``` + +Con esto podríamos ya lanzar nuestro primer _worker_ ejecutando el comando: `celery -A tasks worker --loglevel=INFO` + +Esto nos vale para probar, en un entorno de producción nos interesa lanzar los _workers_ como servicio (ver [Daemonization](https://docs.celeryq.dev/en/stable/userguide/daemonizing.html#daemonizing)) + +Ahora podríamos abrir una sesión con el intérprete de Python y lanzar una tarea al _worker_ con + +```python +from tasks import add +add.delay(4, 4) +``` + +### Django + +Como de costumber creamos un entorno virtual: + +```bash +pyenv virtualenv 3.9.7 ve_django +myve +pip install Django +``` + +Creamos un directorio para las pruebas y le asignamos el *virtualenv* local. + +```bash +amd Django +pyenv local ve_django +``` + +Para empezar hacemos el [tuto oficial](https://docs.djangoproject.com/en/4.0/intro/) + +* `python -m django version` nos permite ver la versión de Django (la 4.0.5) +* `django-admin startproject mysite` nos permite crear un nuevo directorio de proyecto con su estructura característica +* `python manage.py runserver` ejecuta el server, en nuestro caso vale con `./manage.py runserver` +* `python manage.py runserver 8000` ejecuta el server en el puerto 8000 +* `python manage.py runserver 0:8000` ejecuta el server en el puerto 8000 en todas las IP del servidor +* `python manage.py startapp polls` crea el directorio para la aplicación `polls`, las aplicaciones se crean todas en el mismo directorio donde reside el `manage.py` + +#### views en Django + +Para crear una vista: + +- definimos una función en el fichero `views.py` de nuestra aplicación +- en el fichero `urls.py` de nuestra aplicación, asociamos la función a un `path` +- en el fichero `urls.py` de nuestro site, tenemos que incluir el módulo `polls.urls` que se corresponde con el fichero `polls/urls.py`, para hacer esto siempre usamos `include` (`from django.urls import include, path`) + +La funcion `path` tiene cuatro parámetros: + +**route** + +: es un *string* que contiene un patrón para urls. Cuando __Django__ recibe una petición (_request_) **Django** recorrerá todos los patrones de la lista `urlpatterns` hasta encontrar una que encaje. Al hacer esta búsqueda se busca solo el `path` no se comprueba el dominio que aparece en la petición ni el tipo (`GET` o `POST`) ni los parámetros. + +**view** + +: cuando __Django__ encuentra el patrón que encaja se invoca a la función _view_ asociada a ese patrón, pasando el objeto _HttpRequest_ y cualquier otro valor que haya sido capturado + +**kwargs** + +: se pueden pasar parámetros adicionales en un diccionario + +**name** + +: siempre es práctico asignar un nombre a las URL para poder referirnos a ellas desde cualquier parte + +#### Establecer la base de datos + +En el fichero `mysite/settings.py` es un módulo de Python normal y corriente que almacena distintos valores de configuración de __Django__ en variables. + +Por defecto tenemos configurado usar _SQLite3_ como motor de base de datos. Si queremos usar otra de las bases de datos soportadas tenemos que instalar el _backend_ correspondiente ([ver doc](https://docs.djangoproject.com/en/4.0/topics/install/#database-installation)) + +Si usamos _SQLite3_ en el parámetro `NAME` especificamos el fichero de la base de datos _SQLite3_ + +Una vez establecida la base de datos podemos empezar a definir nuestros modelos (_models_), básicamente son las tablas de la base de dawos con sus metadatos. + +Los modelos se definen en el fichero `polls/models.py`, cada tabla en la base de datos se corresponde con una clase en el fichero. + +Cuando tenemos los modelos definidos necesitamos ejecutar dos pasos adicionales: + +* Dar de alta nuestra app en el fichero `mysite/settings.py` +* Activar los modelos, con la operación que en Django se denomina _Migración_. Las migraciones quedan codificadas en ficheros python en el directorio `migrations`de la aplicación correspondiente. + +Comandos: + +- `python manage.py makemigrations polls` prepara las migraciones y las deja listas para ejecutarse en el directorio `migrations` de la aplicación correspondiente. +- `python manage.py sqlmigrate polls 0001` nos dice que comandos sql corresponden a la migración. Son los comandos que se ejecutarán en la base de datos cuando ejecutemos la migración. +- `python manage.py check` hace comprobaciones sin ejecutar la aplicación +- `python manage.py migrate` ejecuta la migración (de hecho ejecuta todas las que haya pendientes) +- `python manage.py shell` nos abre un shell para hacer experimentos + +#### Django admin + +`python manage.py createsuperuser` nos permite crear un usuario administrador para nuestra instancia de Django, podemos acceder al interfaz de administración en la dirección + +Para que nuestra applicación sea accesible desde el interfaz de administración de Django, es necesario modificar el fichero `polls/admin.py` para que incluya las siguientes lineas: + +```python3 +from django.contrib import admin + +from .models import Question + +admin.site.register(Question) +``` + +#### Nuevas vistas + +Para generar nuevas vistas: + +- Definimos la nueva función en el fichero `polls/views.py` +- Añadimos las nuevas url a los `urlpatterns` definidos en el fichero `polls/urls.py` + +Cada vista tiene que lograr uno de dos objetivos: o bien genera un _HttpResponse_ válido o bien devuelve una excepción como por ejemplo `Http404` + +A parte de eso la vista puede hacer todo tipo de cosas. Leer registros de una base de datos, generar ficheros de cualquier tipo, usar el sistema de _Templates_ de Django, etc etc. + + + +### Bibliografía + +* [El libro de Ekaitz](https://gitlab.com/ElenQ/Publishing/python) que hemos "seguido" en este curso +* [La traducción al gallego](https://gitlab.com/brico-labs/curso_python/-/tree/gl) del mismo libro, elaborada por asistentes al curso +* El libro del Dr. Chuck. Los cursos de este hombre en Coursera se podían seguir como oyente de forma gratuita y están muy bien para + empezar. En todo caso [aquí](http://do1.dr-chuck.com/pythonlearn/) tenéis un libro suyo (disponible en castellano) + + +### Batiburrillo de enlaces + +* [Pilulas para o teletraballo](https://www.mancomun.gal/noticias/pilulas-para-o-teletraballo/) +* [Maestro das traducións](https://pad.disroot.org/p/curso_python_memoria.md) +* [Portal das palabras](https://portaldaspalabras.gal/) +* [Apertium translator](https://www.apertium.org/index.glg.html?dir=spa-glg#translation) +* [Python with VCS](https://code.visualstudio.com/docs/python/python-tutorial) +* [Select venv with VCS](https://code.visualstudio.com/docs/python/environments) +* [Decoradores ref 1](https://jeffknupp.com/blog/2013/11/29/improve-your-python-decorators-explained//) +* [Decoradores ref 2](https://www.thecodeship.com/patterns/guide-to-python-function-decorators/) + +Para mais adiante: +* [Flask megatutorial](https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-now-with-python-3-support) +* [Micropython IOT](https://blog.miguelgrinberg.com/post/micropython-and-the-internet-of-things-part-i-welcome) +* [npyscreen TUI en python](https://medium.com/@ValTron/create-tui-on-python-71377849879d) +* [Multithreading in python](https://www.toptal.com/python/beginners-guide-to-concurrency-and-parallelism-in-python) +* [Dash analytical web app (sin javascript)](https://pypi.org/project/dash/) +* [Estadísticas en python](https://www.learntek.org/blog/python-statistics-fundamentals/) +* [Cheatsheet Python](https://gto76.github.io/python-cheatsheet/) +* [Astronomia con python](https://opensource.com/article/19/10/python-astronomy-open-data) +* [Mobil programs in python](https://beeware.org/) + +* [Tutorial de Kivy](https://www.simplifiedpython.net/python-kivy-tutorial-for-beginners-getting-started/) + +Problemas instalando *Kivy* o *Pygame*: +* En [esta página](https://kivy.org/doc/stable/installation/installation-linux-venvs.html#installation-in-venv) en el web oficial, parece que se detallan las dependencias de kivy. +* Para instalar pygame o kivy parece que hay dependencias con sdl2 + En esta otra [página](https://pythonprogramming.altervista.org/how-to-install-pygame-in-python-3-8/) explican como instalar pygame en python 3.8 + +No se vayan todavía... + +* [Think DSP](https://github.com/AllenDowney/ThinkDSP) +* [Task queues in python](https://www.fullstackpython.com/task-queues.html) +* [SICP in Python](https://wizardforcel.gitbooks.io/sicp-in-python/content/1.html) +* [Dive into python 3](https://diveintopython3.net/) + +* [Toga](https://toga.readthedocs.io/en/latest/) +* [Kivy](https://kivy.org) +* [Dash](https://dash.plotly.com/) +* [Celery](https://medium.com/swlh/python-developers-celery-is-a-must-learn-technology-heres-how-to-get-started-578f5d63fab3)) + +* [Server Side programming with web2py](https://www.youtube.com/watch?v=zmSOnKkm8Y0&list=PL5E2E223FE3777851) +* [Web2py web development](https://www.youtube.com/watch?v=dHu8O1gZOl0&list=PLRMNVXvt79o0VUcNSWAmES3c_36BTEQRr&index=2) + + + +* [Python Hacks](https://medium.com/@Nibba2018/python-life-hacks-for-everyday-use-ba6fc4b8c9ac) +* [Abstract Classes](https://medium.com/techtofreedom/abstract-classes-in-python-f49cf4efdb3d) +* [Smart Contract Blockchain in Python](https://medium.com/better-programming/how-to-write-smart-contracts-for-blockchain-using-python-part-2-99fc0cd43c37) + +* [Emacs as Python IDE](https://medium.com/analytics-vidhya/managing-a-python-development-environment-in-emacs-43897fd48c6a) +* [Emacs pyenv](http://rakan.me/emacs/python-dev-with-emacs-and-pyenv/) + + + diff --git a/content/posts/notes_general/notes_rlang.md b/content/posts/notes_general/notes_rlang.md new file mode 100644 index 0000000..05b1b42 --- /dev/null +++ b/content/posts/notes_general/notes_rlang.md @@ -0,0 +1,24 @@ +--- +weight: 4 +title: "Notas de R (lenguaje de programación)" +date: 2022-03-02T17:11:10+0100 +draft: true +summary: "Notas sobre R el lenguaje de programación especializado en estadísticas" +categories: + - notes +tags: + - rlang + - programacion +--- + + + + +## Referencias + +- [A Short R Tutorial](https://strata.uga.edu/software/pdf/Rtutorial.pdf) (pdf) +- [R: A self-learn tutorial](https://gsp.humboldt.edu/OLM/R/Tutorials/BestFirstRTutorial.pdf) (pdf) +- (evaluar) +- W3Schools (evaluar) +- Tutorialspoint (evaluar) +- Guru99 (evaluar) diff --git a/content/posts/notes_general/notes_rocketchat.md b/content/posts/notes_general/notes_rocketchat.md new file mode 100644 index 0000000..b876f96 --- /dev/null +++ b/content/posts/notes_general/notes_rocketchat.md @@ -0,0 +1,201 @@ +--- +weight: 4 +title: "Instalación de Rocket Chat en Docker" +date: 2021-03-07T17:24:04+0100 +draft: false +summary: "Instalación de Rocket Chat en Docker" +categories: + - notes +tags: + - docker + - traefik + - rocketchat +--- + +Como instalar el servicio Rocket.Chat sobre Docker con Traefik v2. + + + +# Rocket Chat sobre Docker tras Traefik v2 + +[Rocketchat](https://rocket.chat/) es un interesante servicio de chat +que [es software libre y +abierto](https://github.com/RocketChat/Rocket.Chat/blob/master/LICENSE). + + +## Directorios de trabajo + +{{< admonition type=abstract title="Referencias" state=open >}} + + +Para configurar el servicio _Rocketchat_ seguimos las siguientes +referencias: + +- [github del proyecto rocketchat](https://github.com/RocketChat/Rocket.Chat) +- [Realtarget traefik-docker-stack](https://github.com/realtarget/traefik2-docker-stack) + +{{}} + +Para realizar la instalación de ***Rocketchat*** suponemos que tenemos +un servicio _Docker_ instalado con _Traefik v2_ funcionando como proxy +inverso. Por ejemplo como describimos [aquí]({{< relref +"notes_traefik/#ejemplo03-una-configuraci%C3%B3n-sencilla-para-empezar-con-traefik-en-producci%C3%B3n" +>}}) + +Partimos del fichero `docker-compose.yml` que nos proponen en [el +github del +proyecto](https://github.com/RocketChat/Rocket.Chat/blob/develop/docker-compose.yml) + +Para ello creamos un directorio para nuestra configuración _docker_ del _Rocket Chat_: + +```bash +mkdir -p ~/work/010_rocketchat/{mongo, rocketchat} +touch ~/work/010_rocketchat/docker-compose.yml +``` + +## Configuración del contenedor Docker + +Modificamos el fichero que nos proponen en el github: + +- Fijamos las versiones concretas a utilizar de las imágenes _Rocket.Chat_ y + _Mongodb_ +- Configuramos el host para que use HTTPS +- Conectamos los contenedores de _Rocket.Chat_ y _Mongodb_ a la red + ___backend___ y les asignamos direcciones IP manualmente + (___backend___ es una red interna de nuestra configuración _Docker_) +- Conectamos el contenedor de _Rocket.Chat_ a la red ___frontend___ + (puesto que tendrá que comunicarse con _Traefik_), asignando la + dirección IP explicitamente. +- El servicio estará disponible a través de _Traefik_ así que quitamos + la sección `ports` (no queremos puertos mapeados a nuestro _host_) y ... +- añadimos la sección `labels` a la definición del servicio + `rocketchat` en nuestro fichero `docker-compose.yml` para definir + los enrutamientos _Traefik_ al servicio _Rocket.Chat_ +- No es necesario añadir el servidor de correo SMTP al entorno, + podremos configurarlo sin problemas desde el interfaz de + administración del chat una vez lanzados los contenedores de + servicios. +- Hemos corregido la activación de la replica de la base de datos + _Mongo_, en realidad no estamos replicando nada, pero queda todo + listo para dar redundancia en el futuro si así lo decidimos. (Más + info + [aquí](https://docs.mongodb.com/manual/tutorial/deploy-replica-set/)) + +```yaml +version: '3' + +services: + rocketchat: + image: rocketchat/rocket.chat:3.12.0 + container_name: rocketchat + networks: + frontend: + ipv4_address: 172.21.0.100 + backend: + ipv4_address: 172.20.0.100 + command: > + bash -c + "for i in `seq 1 30`; do + node main.js && + s=$$? && break || s=$$?; + echo \"Tried $$i times. Waiting 5 secs...\"; + sleep 5; + done; (exit $$s)" + restart: unless-stopped + volumes: + - ./rocketchat/uploads:/app/uploads + environment: + - PORT=3000 + - ROOT_URL=https://chat.comacero.com + - MONGO_URL=mongodb://mongo:27017/rocketchat + - MONGO_OPLOG_URL=mongodb://mongo:27017/local +# - MAIL_URL=smtp://mail.server.com +# - HTTP_PROXY=http://proxy.domain.com +# - HTTPS_PROXY=http://proxy.domain.com + depends_on: + - mongo +# ports: +# - 80:3000 + labels: + - traefik.enable=true + - traefik.http.routers.rocket.entrypoints=https + - traefik.http.routers.rocket.rule=Host(`chat.yourdomain.com`) + - traefik.http.routers.rocket.tls.certresolver=letsencrypt + - traefik.http.services.rocket.loadbalancer.server.port=3000 + - traefik.docker.network=frontend + + mongo: + image: mongo:4.0 + container_name: mongo + networks: + backend: + ipv4_address: 172.20.0.110 + restart: unless-stopped + volumes: + - ./mongo/data/db:/data/db + - ./mongo/data/dump:/dump + command: mongod --smallfiles --oplogSize 128 --replSet rs0 --storageEngine=mmapv1 + + # this container's job is just run the command to initialize the replica set. + # it will run the command and remove himself (it will not stay running) + mongo-init-replica: + image: mongo:4.0 + container_name: mongo_replica + networks: + - backend + command: > + bash -c + "for i in `seq 1 30`; do + mongo mongo/rocketchat --eval \" + rs.initiate({ + _id: 'rs0', + members: [ { _id: 0, host: 'mongo:27017' } ]})\" && + s=$$? && break || s=$$?; + echo \"Tried $$i times. Waiting 5 secs...\"; + sleep 5; + done; (exit $$s)" + depends_on: + - mongo + +networks: + frontend: + external: true + backend: + external: true +``` + +## Configuraciones adicionales + +- Evidentemente necesitamos una entrada en nuestra zona DNS que apunte + al chat ya que hemos usado un subdominio. +- En la documentación de _Rocket Chat_ nos recomiendan añadir la linea + `chat.yourdomain.com` a nuestro fichero `/etc/hosts` de forma que + apunte a `127.0.0.1` (_localhost_) +- Necesitamos un servidor SMTP de correo. Nuestro proveedor del + dominio lo da gratis para volúmenes bajos de correo y es el que + hemos usado, pero también hay algunas opciones en Internet que se + pueden usar. +- Una vez arrancado el servicio daremos de alta __un usuario + administrador__ para el _Rocket Chat_ conectando a +- Configuramos las opciones del servidor de correo (para poder + verificar las direcciones de correo de los usuarios que se registren + y poder enviar contraseñas en caso de reseteo) +- Nos damos de alta en [Rocket Chat Cloud](https://cloud.rocket.chat) + que nos proporcionará gratis servicios de doble factor de + autenticación (via correo), para nuestro servicio. +- Configuramos la conexión de nuestro servidor _Rocket Chat_ con el + _cloud.rocket.chat_. Basta con añadir el _token_, que nos dan en el + paso anterior, mediante el interfaz de administración de nuestro + servicio. +- Revisamos __cuidadosamente__ como queremos dejar la configuración + para el registro de nuevos usuarios. Parece recomendable activar al + menos que solo se puedan loguear usuarios con el correo verificado y + probablemente deberíamos activar la aprobación manual de nuevos + usuarios. + +{{< admonition type=warning title="Seguridad" state=open >}} + +Como siempre que abrimos servicios a internet, conviene ir con cuidado +y tomar todo tipo de precauciones. Si dejamos abierto el registro de nuevos usuarios nos podemos encontrar con una avalancha de peticiones de nuevos usuarios por parte de bots. + +{{}} diff --git a/content/posts/notes_general/notes_ros.md b/content/posts/notes_general/notes_ros.md new file mode 100644 index 0000000..c0ed6d9 --- /dev/null +++ b/content/posts/notes_general/notes_ros.md @@ -0,0 +1,225 @@ +--- +weight: 4 +title: "Apuntes de Robot Operating System (ROS)" +date: 2023-02-27T16:42:06+0100 +draft: false +summary: "Apuntes de ROS" +categories: +- notes +tags: +- ros +- docker +- robot +--- + +## ¿Qué es ROS? + +{{< admonition type=info title="Referencias" open=true >}} + +- [ROS2 vs ROS1](https://medium.com/@oelmofty/ros2-how-is-it-better-than-ros1-881632e1979a) +- [ROS1 vs ROS2](https://roboticsbackend.com/ros1-vs-ros2-practical-overview) Practical Overview from Robotics Backend +- [Robot Operating System 2: Design, architecture, and uses in the wild](https://www.science.org/doi/10.1126/scirobotics.abm6074) + +{{< /admonition >}} + + +_Robot Operating System_ (ROS) es un _framework_ para programar __robots__, nos da facilidades para programar tareas de diferentes tipos como navegación (usando el stack de navegación de ROS), manipulación (usando _moveit_), percepción (usando por ejemplo [PCL]^(Point Cloud Library)) + +### Conceptos básicos de ROS + +__Nodos__ + +: Son los procesos básicos en ROS, cada nodo representa un proceso independiente dentro del _ROS stack_. Los nodos pueden comunicarse entre si de tres maneras: + + - ___ROS topics___ (_publisher/subscriber_) posibilitan a un nodo a publicar mensajes para que los lean otros nodos que se suscriban al _topic_ + - ___ROS services___ (_request/response_) posibilita que un nodo cliente lance peticiones (_request_) a otro nodo servidor solicitando un servicio. Una vez que se completa la tarea el nodo servidor responde (_response_) enviando el resultado de la petición. + + - ___ROS actions___ (_action/feedback/result_) Con este método un nodo cliente puede enviar una petición de acción a otro nodo servidor. Mientras el servidor está ejecutando la acción envía información continuamente al cliente del progreso de la acción. Una vez completada la tarea, el servidor envia el resultado al cliente, este sería el producto final de la acción. Las "acciones" se parecen a los servicios, la diferencia es la realimentación continua que el servidor da al cliente mientras ejecuta la acción. + +___Parameter Server _ + +: En este servidor podemos almacenar parámetros globales que todos los nodos pueden consultar. + +### ROS1 y ROS2 + +_ROS Noetic_ es la última versión de ROS 1, y está destinada a ser la última versión de ROS 1. _ROS Noetic_ utiliza ya Python 3 y está basada en Ubuntu 20.04. Hay un montón de literatura, tutoriales y ejemplos basados en ROS1 pero creo que lo mejor es centrarse directamente en ROS2. + +ROS2 es la nueva versión de ROS. La motivación principal de ROS2 es ser un sistema operativo orientado a ser usado en el mundo real y no un sistema operativo de investigación y desarrollo como era ROS1, sus objetivos declarados son: + +__Seguridad__ + +: Tiene que ser seguro y usar la encriptación adecuada siempre que sea necesario. + +__Sistemas Embebidos__ + +: ROS2 tiene que poder ejecutarse en sistemas embebidos. + +__Diversidad de red__ + +: Es necesario que pueda conectarse y comunicarse usando todo tipo de redes desde LAN a redes satelitales, para adecuarse a cualquier entorno en el que se usen robots. + +__Computación en Tiempo Real__ + +: Tiene que ser capaz de llevar a cabo [RTC]^(Real Time Computing) de forma fiable, dado que esta es crucial para la robótica. + +__Orientado al producto__ + +: Tiene que cumplir con los estándares industriales para usarlo en productos comerciales + + +ROS2 implica cambiar muchas cosas: + +- Usa [DDS](https://en.wikipedia.org/wiki/Data_Distribution_Service) como protocolo de red para todas las comunicaciones. + - Prescinde del _ROS Master_ que actuaba como coordinador de nodos en ROS1. Usa funcionalidades del protocolo [DDS]^(Data Distribution Service) para que los nodos se descubran. + - Es más robusto frente a problemas de comunicación que ROS1, también gracias a la funcionalidad de DDS +- ROS2 es multiplataforma, corre de forma nativa en Linux, Windows y MacOS +- Los nodos de ROS2 pueden correr en un mismo proceso y su ciclo de vida es gestionable +- Las bibliotecas de ROS2 comparten una implementación común (escrita en C) +- Incluye un bridge (_ROS1 bridge_) para facilitar la comunicación con, y migración de, antiguos sistemas. + +## Tutoriales de ROS2 + +- Los tutos oficiales +- +- (de pago en tutorialspoint) +- de pago en Udemy +- Lista en youtube aparentemente del mismo autor +- Un curso condensado del mismo autor + +### El tutorial oficial + +1. Empezar con [las instrucciones de instalación](https://docs.ros.org/en/humble/Installation.html) +2. Seguir con [los tutoriales](https://docs.ros.org/en/humble/Tutorials.html) + +## Probando ROS2 con Docker + + + + + +## Probar ROS1 con Docker + +{{< admonition type=info title="Referencias" open=true >}} + +- [Docker and Ros](https://roboticseabass.com/2021/04/21/docker-and-ros/) by Robotic Sea Bass +- [Getting Started with ROS and Docker](https://wiki.ros.org/docker/Tutorials/Docker) de la wiki de ROS +- [Gráficos en Docker](https://wiki.ros.org/docker/Tutorials/GUI) de la wiki de ROS +- [Install ROS Noetic with Docker](https://varhowto.com/install-ros-noetic-docker/) + +{{< /admonition >}} + + +Gracias a Docker podemos usar ROS fácilmente en cualquier linux (aunque no sea Ubuntu) + +Lanzar un completo _ROS Noetic_ en Docker es tan simple como: + +```bash +docker pull osrf/ros:noetic-desktop-full + +# Si queremos una sesión interactiva: +docker run -it osrf/ros:noetic-desktop-full bash +``` + + +Imágenes Docker de ROS disponibles: + +__noetic-ros-core__ + +: Esta imagen nos permite usar paquetes básicos de ROS: podremos publicar y suscribirnos a nodos ROS, invocar servicios ROS y lazar ficheros ROS. + +__noetic-ros-base__ + +: Basada en la anterior. Añade algunos paquetes esenciales como: _actionlib_, _dynamic reconfigure_, _nodelets_ y _pluginlib_. + +__noetic-robot__ + +: Basada en __noetic-ros-base__ añade los metapaquetes `robot`y `viz`, así como la mayor parte de los paquetes utilizados en los tutoriales oficiales. Esta sería la imagen mínima para hacer los tutoriales. También sería la imagen ideal para instalar en un robot. + +__noetic-perception__ + +: Esta imagen incluye [PCL]^(Point Cloud Library), la biblioteca `perception_pcl` y bibliotecas relacionadas con el procesamiento de imágenes como: + + - `image_common` + - `image_pipeline` + - `image_transport_plugins` + - `laser_pipeline`00 + +__noetic-desktop__ + + +: Es similar a la anterior (__noetic-robot__) pero no está basada en ella. + +__noetic-desktop-full__ + +: Basada en la anterior pero además añade paquetes de percepción y simulación, tales como `Gazebo` y `PCL`. + + +## Experimento + +```bash +# Si queremos una sesión interactiva: +docker run -it osrf/ros:noetic-desktop-full bash + +``` + +Dentro del contenedor actualizamos + +```bash +apt update +apt upgrade +apt install python3-rosdep python3-rosinstall python3-rosinstall-generator python3-wstool build-essential +apt install python3-rosdep +``` + + + +Un dockerfile: + +```dockerfile +FROM: osrf/ros:noetic-desktop-full + + +RUN: apt-get update && apt-get upgrade -y +RUN: apt-get install python3-rosdep python3-rosinstall python3-rosinstall-generator python3-wstool build-essential python3-rosdep + + + +``` + +```bash +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' + + +# Lanzar Xephyr en el DISPLAY :1 +Xephyr -ac -screen 800x600 -br -reset -terminate 2> /dev/null :1 & + +docker run -it --name correMundos pabloImage roscore + + + +world_files=`docker exec -it correMundos 'ls' '-1' '/opt/ros/noetic/share/stage/worlds/' |grep '\.world' ` + +if [ ${#world_files[@]} -eq 0 ]; then + echo "No se encontraron archivos con la extensión .world en este directorio." +else + echo "Archivos .world encontrados:" + for i in "${!world_files[@]}"; do + echo "$((i+1)): ${world_files[i]}" + done + + read -p "Ingrese el número del archivo que desea ejecutar: " selection + case $selection in + [1-${#world_files[@]}]) + selected_file=${world_files[$((selection-1))]} + echo "Ejecutando archivo $selected_file..." + docker exec -it correMundos "rosrun stage_ros stageros /opt/ros/noetic/share/stage/worlds/$selected_file" + ;; + *) + echo "Selección inválida. Saliendo del programa." + ;; + esac +fi + +``` diff --git a/content/posts/notes_general/notes_shotcut.md b/content/posts/notes_general/notes_shotcut.md new file mode 100644 index 0000000..c2e06c0 --- /dev/null +++ b/content/posts/notes_general/notes_shotcut.md @@ -0,0 +1,71 @@ +--- +weight: 4 +title: "Apuntes de Shotcut" +date: 2022-06-16T11:10:34+0200 +draft: true +summary: "Apuntes incompletos del editor de video Shotcut" +categories: +- notes +tags: +- shotcut +- edicionVideo +--- + +{{< admonition type=warning title="APUNTES INCOMPLETOS" open=true >}} + +Estos apuntes **no están** completos. + +{{< /admonition >}} + +## Referencias + + + +## Varias notas + +* Todos los paneles se pueden mover, fijar, cerrar, etc. etc. +* Podemos usar `Ctrl+z` para deshacer. Tenemos opciones _Undo_ y _Redo_ y tenemos un _History Panel_ para hacer y rehacer acciones. +* Se pueden reabrir con el menú _Panels_ +* Nuestras fuentes se añaden en el _Playlist Panel_ +* El _Timeline_ tiene varias pistas (_Tracks_) en cada pista podemos ir añadiendo tomas (_Shots_) + * Podemos añadir nuevos _Tracks_ de audio o de video + * Podemos hacer zoom con la rueda del ratón + * En cada _Track_ podemos: + * silenciar el sonido o ocultar el video (evidentemente afectan al resultado final) + * bloquearlo para impedir cambios accidentales. + * Podemos arrastrar cualquier toma (_Shot_) dentro de su _Track_ o a otra pista diferente. Si arrastramos una toma de video a una pista de audio sólo se usará el audio de ese video. + * Si arrastramos dos tomas de forma que se superpongan se creará una transición entre tomas. + * Podemos editar los parámetros de la transición haciendo click derecho sobre ella + * Si arrastramos la esquina superior izquierda de una toma podemos añadir una transición para que empiece "suavemente" + * Para cada toma (_Shot_) si la seleccionamos (marco rojo rodeando la toma), podremos: + * Ver y editar sus propiedades (cambiando la relación de aspecto, el sync del audio o los metadatos) podemos cambiar su velocidad o su duración (recortándolo) + * Podemos cortar una toma en dos usando los iconos en el _Timeline_ o con el botón derecho del ratón. +* El panel de _Preview_ tiene dos modos de funcionamiento, en pestañas separadas: + * _Source_ es para previsualizar fuentes del _Playlist Panel_ + * _Project_ es para previsualizar el proyecto actual (la _Timeline_) + * En este modo veremos el _marker_ en el _Timeline_, podemos moverlo con el ratón o con los cursores para ir _frame_ a _frame_ + * Tenemos la posibilidad de activar un _Grid_(hay varias configuraciones) o hacer zoom. +* Con el _Peakmeter Panel_ podemos comprobar los niveles de sonido +* Con el menú _View::Scopes_ podemos activar varias herramientas de visualización de sonido y video +* Podemos aplicar muchos filtros de todo tipo desde el _Filters Panel_ permiten todo tipo de operaciones de corrección de imágenes y sonido o inclusión de texto 3D o 2D. + * El efecto de los filtros se puede limitar con marcas de tiempo. +* Cuando tengamos todo listo podemos exportar el video (hay muchas plantillas de exportación) + + +### Notas al arrancar el programa + +**Playlist** + +: Situada en el tercio izquierdo. + - Con doble click podemos abrir cualquier video del _Playlist_ en el reproductor + - Se pueden previsualizar clips sin necesidad de añadirlos al _Playlist_ + - Para ajustar cualquier item de la _Playlist_ lo abrimos con doble click, hacemos los cambios y le damos al botón de actualizar (no veo como hacer esto) + - Podemos reordenar los items con _Drag-n-drop_ + +: + +## Borrador de flujo de trabajo + +* Abrimos un nuevo proyecto especificando el _videomode_ (_framerate_ y resolución) +* Importamos nuestras fuentes (videos, audios, imágenes) en el panel _Playlist_ (lo tengo a la izquierda) +* Arrastramos nuestras fuentes desde el panel _Playlist_ hasta el _Timeline_ (lo tengo abajo) diff --git a/content/posts/notes_general/notes_synth.md b/content/posts/notes_general/notes_synth.md new file mode 100644 index 0000000..b9b35c5 --- /dev/null +++ b/content/posts/notes_general/notes_synth.md @@ -0,0 +1,293 @@ +--- +weight: 4 +title: "Apuntes de Sintetizadores DIY" +date: 2022-07-29T13:53:56+0200 +draft: false +math: true +summary: "Apuntes de Sintetizadores" +categories: +- notes +tags: +- electrónica +--- + +## Conceptos + +VCO + +: _Voltage Controlled Oscillator_, un oscilador controlado por tensión. + + + + + + +Es una de las fuentes de sonido en el sintetizador. El oscilador controlado por tensión (**V**oltage **C**ontroled **O**scillator) puede producir diferentes formas de onda simples o complejas. + +Algunos VCO permiten seleccionar el tipo de onda de salida, otros generan diferentes tipos de onda de salida simultanemente, y las ofrecen en sendas tomas de salida. + +Los VCO tienen una entrada CV (**C**ontrol **V**oltage Input) que permite controlar la frecuencia de la nota producida (de ahí su nombre). La señal CV puede venir de cualquier fuente. Podemos usar un controlador de teclado que genere los voltajes correspondientes a notas de la escala, pero también podemos obtener los voltajes de control de cualquier fuente que se nos ocurra: de un sensor, de una fuente aleatoria, etc. etc. + +Para usar un VCO para generar notas musicales tenemos varios estándares establecidos: + +**Voltios por octava** + +: Cada voltio equivale a una octava. Propuesto por Moog en la década de los 60. Este es el estándar mejor implantado hoy en dia y casi todos los sintetizadores lo usan. + +**Hertzios por octava** + +: Subir una octava equivale a doblar el voltaje de control y bajar una octava equivale a reducirlo a la mitad. Un estándar muy utilizado en Yamaha y Korg. + +Además de la entrada VC, el VCO puede tener más entradas. + + +LFO + +: _Low Frequency Oscillator_ + + + +## Proyectos + +### Atari Punk Console + +El Atari Punk Console (APC) es uno de los proyectos recomendados para los principiantes ya que es muy fácil de hacer, pero el sonido no es muy melodioso que digamos. El nombre original era "Sintetizador de Sonido" ([Forrest M. Mims III - 1984](http://www.forrestmims.org/)). Mas tarde el grupo Kaustic Machines (la única referencia que encontré es [esta](https://compiler.kaustic.net/machines/back.html), y por si acaso me he salvado todos los esquemas que hay en la página), le puso el nombre de Atari Punk Console por que el sonido que produce se parece al de las primeras consolas Atari. + +#### IC555 (Circuito integrado) + +El montaje de la APC está basado en el uso de [NE555](https://es.wikipedia.org/wiki/Circuito_integrado_555) posiblemente uno de los chips más versátiles de la historia de la electrónica. + +En principio el circuito divide la tensión Vcc en tres partes iguales gracias a su divisor de tensión con tres resistencias de 5K (lo de que se llama 555 por estas tres resistencias es una leyenda desmentida por su creador) + +- _Vcc_ y _GND_ son los pines de alimentación. El voltaje de alimentación típico va de 4.5 V hasta los 16 V +- _Output_ (pin 3), aquí tenemos la salida del integrado, la salida alta es tipicamente: $V_{cc} - 1.7 V$, la salida baja +- _Trigger_ (pin 2) cuando en la entrada _Trigger_ tenemos una tensión menor de 1/3 de Vcc, el integrado activa la salida (y yo diria que pone la pata _Discharge_ en alta impedancia) +- _Threshold_ (pin 6), cuando la tensión en la entrada _Threshold_ es mayor que 2/3 de Vcc, el integrado desactiva la salida, y ademas pone el terminal _Discharge_ (pin 7) a tierra (GND) +- _Reset_ (pin 4), Si ponemos _RST_ a un voltaje cercano a cero (por debajo de 0.7 V) pone la salida del circuito a nivel bajo. En caso de no usar esta patilla lo mejor es conectarla a _Vcc_ para evitar reseteos indeseados. +- _CV_ (pin 5) es el pin de control de voltaje, el voltaje aplicado a esta patilla puede variar entre $V_{cc} - 1.7 V$ hasta prácticamente cero, este voltaje nos permitiría alterar los tiempos que definen los pulsos de salida o incluso conseguir salidas en rampa. + +##### Configuración astable (no estable) del 555 + +Esta configuración no presenta un estado estable, tendremos un pulso cuadrado a la salida. + +Es una configuración que podemos distinguir fácilmente por las dos resistencias en serie con un condensador y por que los pines _Threshold_ y _Trigger_ están conectados. +Los pines de _Reset_ y _Control Voltage_ no se usan, por eso conectamos la patilla _Reset_ a $V_{cc}$ y la patilla _CV_ a tierra a través del condensador C2. + +{{< figure src="/images/synthes/ne555_astable.jpg" title="ne555 aestable" width=500 >}} + +- En el momento en que alimentamos el circuito el condensador C1 comienza a cargarse con la corriente que pasa a través $R_1 + R_2$ +Mientras el voltaje sea pequeño ($V_{c1} < \frac{1}{3}V_{cc}$) al estar el _Trigger_ por debajo del umbral la salida del circuito estará alta. + +- Cuando el voltaje en el condensador C1 iguale o supere $\frac{2}{3}V_{cc}$, al estar _Threshold_ por encima de su umbral, la salida pasará a valor bajo. El pin _Discharge_ se pondrá a tierra, y el condensador C1 empezará a descargarse a través de $R_2$ + +- Cuando el condensador $C_1$ se descargue hasta $\frac{1}{3}V_{cc}$ de nuevo se activará la entrada _Trigger_ que hará que la salida pase a nivel alto y _Discharge_ pase a estado de alta impedancia. + +Y todo el ciclo se repite de nuevo. + +##### Configuración monoestable del 555 + +{{< figure src="/images/synthes/ne555_monostable.jpg" title="ne555 monoestable" width=500 >}} + +##### Configuración biestable del 555 + + +- [Un modelo simple](https://samvssound.com/projects/synthesizers/atari-punk-console/) +- [El mismo modelo con LFO](https://samvssound.com/tag/stepped-tone-generator/) +- +- +- +- + +### Baby 8 Sequencer (B8BY) + +- [Oh Baby, Baby10 – Build A Classic Analog Music Sequencer](https://hackaday.com/2016/01/14/oh-baby-baby10-build-a-classic-analog-music-sequencer/) +- [Eddy Bergman.com: Synthesizer Build Part 8: 8 step sequencer](https://www.eddybergman.com/2019/12/synthesizer-build-part-8-8-step.html) +- [Hackster.io B8BY](https://www.hackster.io/Jettsette/b8by-baby-8-step-sequencer-c5d055) +- [Atary Punk Console & B8BY](https://www.instructables.com/Atari-Punk-Console-With-a-Baby-8-Step-Sequencer/) +- [Baby 5 sequencer – Moritz Klein inspired version](https://knopslmodular.design.blog/2022/04/27/baby-5-sequencer-moritz-klein-inspired-version/) +- [Atari Punk Console & Sequencer](http://owyheesound.com/sequencer.php) +- + +### Altavoz + +- https://samvssound.com/projects/amplifiers/lm386-power-amplifier/ + +### Velleman voice changer + +- https://diyaudiocircuits.com/velleman-mk171-voice-changer-circuit/ + +### Weird Sound Generator + +### Noise Toaster + +### Construir un eurorack + +* Construir un Eurorack en cinco videos: [Youtube](https://youtu.be/EjDTz8JsX6c) o [Invideous](https://invidious.slipfox.xyz/watch?v=EjDTz8JsX6c) +* +* [Construcción de un Eurorack I](https://invidio.xamh.de/watch?v=6mVbi8B3usY) +* [Construcción de un Eurorack II](https://invidio.xamh.de/watch?v=NLljeZpyGEY) +* [Z-rails A](https://www.gear4music.es/es/Grabacion-y-Ordenadores/Tiptop-Audio-Z-Rail-40HP-Pair-Silver/218F) +* [Z-rails Thomann](https://www.thomann.de/es/tiptop_audio_z_rails_84hp_silver.htm) +* [Tienda en Holanda](https://www.etsy.com/shop/ModularSynthLab?ref=l2-about-shopname) + +### Sintes digitales + +#### TeensyPoly6 + +- [TeensyPoly6 en github](https://github.com/albnys/TeensyPoly6) +- [Video del TeensyPoly6](https://invidious.esmailelbob.xyz/watch?v=Exk_K2VwGu0) +- [Tsynth](http://electrotechnique.cc/) Basado en Teensy 4.1 no parece lo mismo + +#### Polykit +- [Github del proyecto](https://github.com/polykit) +- +- [Github de Crayg Barnes](https://github.com/craigyjp) Hace variantes del Polykit + +#### Bomtempi +- +- + +#### FPGA +- + + + +## Referencias + +- [Sam vs Sound](https://samvssound.com/) +- [Sam vs Sound: Atari Punk Console](http://samvssound.com/projects/synthesizers/atari-punk-console/) +- [Atari Punk Console Improved](https://samvssound.com/tag/stepped-tone-generator/) +- [Synth DIY Wiki](https://sdiy.info/wiki/Main_Page), parece muy centrada en el [CatGirlSynth](https://sdiy.info/wiki/CatGirl_Synth#The_CGS_modules), una colección de módulos publicada como CC-BY-SHA-NC. La [página original de CGS](https://web.archive.org/web/20070202151335/http://cgs.synth.net/) se ha archivado en _archive.org_ +- [Music From Outer Space](http://musicfromouterspace.com/) Probablemente una de las más conocidas, es la página del autor del libro _Make: Analog Synthesizers_ +- [Music From Outer Space: Projects](http://musicfromouterspace.com/index.php?MAINTAB=SYNTHDIY&PROJARG=ALIENSCREAMER%2FALIENSCREAMER.php&CATPARTNO=ALIENSCREAMERPCB) +- [MFOS: Weird Sound Generator](http://musicfromouterspace.com/index.php?CATPARTNO=WSG001&PROJARG=WSG2010%2Fwsg_page1.html&MAINTAB=SYNTHDIY&SONGID=NONE&VPW=1910&VPH=807) El proyecto que recomiendan en MFOS para empezar. +- [Especificaciones Eurorack](https://sdiy.info/wiki/Eurorack) (Esta wiki parece abandonada) +- [Eurorack](https://intellijel.com/support/eurorack-101/) Bastante bien explicado +- [www.synth.net](https://web.archive.org/web/20070702154444/http://www.synth.net/) preservada en _archive.org_. En su dia se autodefinían como el _Recurso Definitivo para Sintetizadores_, alojaba varios proyectos diferentes. +- [Minimo](https://minimosynth.com/) +- [OTTO](https://github.com/bitfieldaudio/OTTO) un sintetizador open hardware inspirado en el OP-1 (sea lo que sea OP-1) +- [Una lista de synth abiertos](https://www.synthtopia.com/content/tag/open-hardware/) +- [The Synthesizer Academy](https://synthesizeracademy.com/) + +### Videos + +- [Building a DIY Kit](https://www.youtube.com/watch?v=cduc68TVWJ4) Un canal bastante interesante +- [DIY Minimoog Clone](https://www.youtube.com/watch?v=PATlf0W3m7k) REVISAR +- [Getting Started with Modular Synths](https://www.youtube.com/watch?v=AFhHArHk5JE) Como contruir un Eurorack +- + + +### Para revisar + +- +- +- +- +- +- +- +- +- +- +- [modwiggler](https://www.modwiggler.com/forum/index.php) un foro centrado en el tema de sintetizadores +- [Schemas for Minimoog](https://sites.google.com/site/minimoogwiki/home/notes) +- [Más de Minimoog](http://www.emusic-diy.org/MoogManuals#head-d983034898c0e3ee8d2f85af3aef51ebb66d2744) +- [Más esquemas de Minimoog](http://www.fantasyjackpalance.com/fjp/sound/synth/synthdata/16-moog-minimoog.html) +- [Otro clone de Minimoog](https://661.org/proj/minimoog/) +- [emusic-diy](http://emusic-diy.org/Schematics): Esquemas variados +- [How to Design and Build an Analog Synthesizer from Scracht](https://www.jiisuki.net/reports/howto-build-analog-synth.pdf) Un pdf +- [Minimo Synth](https://minimosynth.com/) Otro synth, pero digital +- [El canal de Ray Wilson en SoundCloud](https://soundcloud.com/raywilson) +- [Electrocumbia en SoundCloud](https://soundcloud.com/drdubastein/electrocumbia) +- [Obras usando el Noise Toaster en SoundCloud](https://soundcloud.com/search?q=noise+toaster) +- [Mateo Mena y sus Hypercubes](https://invidious.esmailelbob.xyz/watch?v=m55dfTXIUgY) +- [Explicación de distintos tipos de jack](https://media.digikey.com/pdf/Application%20Notes/CUI%20Application%20Notes/Audio_Jack_Switches_Schematics.pdf) (es un pdf [aquí](https://www.cuidevices.com/blog/understanding-audio-jack-switches-and-schematics) lo tienen online) + + + + +## Software + +### Puredata + +En Linux Mint se puede instalar _Puredata_ desde los repos, la versión 0.50.2 en el momento de escribir esto: + +```bash +sudo apt install puredata multimedia-puredata + +apt search ^pd- # para ver todas las bibliotecas de puredata disponibles para instalación +``` + +Si queremos tener una versión más actualizada podemos recurrir al ppa oficial: + +```bash +sudo add-apt-repository ppa:pure-data/pure-data +sudo apt update +sudo apt install puredata +``` + +#### ELSE: Una biblioteca muy completa que incluye un tutorial de pd + +Una vez instalada (y probada) la versión 0.52 de Puredata podemos instalar _Else_ desde el menú `Help::Find Externals`, buscamos `else` e instalamos (le va a llevar un ratito, al menos con mi wifi) + +Una vez instalada, tenemos que mover el directorio `live-electronics-folder` que contiene el tutorial a un sitio más adecuado (casi seguro que `~/Documents/Pd` si has seguido las sugerencias durante la instalación). Es preferible dejar el directorio `~/Documents/Pd/externals` para las bibliotecas externas. + +Con eso ya tenemos el tutorial disponible, no tenemos más que abrir los ficheros del tutorial desde el propio _Puredata_. + +#### Referencias + +- [Colección de recursos](https://puredata.info/docs/tutorials/) +- [Programming Electronic Music in Pd](http://www.pd-tutorial.com/english/index.html) de Johannes Kreidler (online) +- [Programando Música Electrónica en Pd](https://lucarda.com.ar/pd-tutorial/index.html) traducción del anterior al castellano (online) +- [The Theory and Technique of Electronic Music](http://msp.ucsd.edu/techniques.htm) de Miller Puckette +- [Live Electronics Tutorial](https://github.com/porres/Live-Electronics-Tutorial/blob/v1.0-rc2/README.md) Este tiene muy buena pinta, pero necesitas tener como mínimo la versión 0.52 de puredata, viene incluido como curso introductorio al instalar ELSE en Puredata. +- [Crear un Synth con Puredata](https://linuxaudio.github.io/libremusicproduction/html/tutorials/creating-simple-synthesizer-pure-data-%E2%80%93-part-i.html) +- [Casper Electronics DIY Synth](https://www.youtube.com/watch?v=FaoJaLmZaL4) +- +- +- + + +### Supercollider + +- [Supercollider](https://supercollider.github.io/) en github. + + +## Podcast + +Vamos a proponer una serie de programas centrados en la música electrónica y sintetizadores analógicos, animando a los que quieran unirse a nosotros en la exploración de este tema e incluso construir algún prototipo con la información disponible. + +¿Qué tiene que ver este tema con el hw libre? ¿o con esta sección? + +Bueno, por un lado la electrónica analógica es un camino interesante para introducirse en el mundillo. La electrónica analógica te permite obtener resultados _inmediatos_ en el sentido de que te ahorras la fase de programar un firmware. Si además puedes disponer de alguna herramienta para visualizar las señales (un osciloscopio) puedes aprender rápidamente un montón de conceptos que llevaría horas explicar usando sólo la teoría. + +Por otro lado, cuando empezamos a investigar la documentación disponible vimos que aunque los sintetizadores analógicos son una tecnología "antigua", que precede en una o dos décadas a toda la revolución de la electrónica digital posterior. El espíritu de muchos (no todos, claro) de los pioneros en la música electrónica es fácil de indentificar con el espíritu del hardware libre en la actualidad. Por ejemplo Ray Wilson (1956-2016) en su página MFOS (una referencia imprescindible) siempre se nego a que sus diseños fuera open source pero: + +Ray was happy to share his circuit designs and knowledge with hobbyists but did not give permission to mass or hand produce what he considered his intellectual property. (MFOS designs are now owned by synthCube). + +Es decir que compartió sus diseños siempre que fueran para uso No Comercial. + +Esta filosofía de compartir conocimientos puede verse en las antiguas páginas web y foros que en muchos casos ya sólo están disponibles en [archive.org](https://archive.org/). Se puede comprobar que los constructores de "Sintes" compartían libremente la información y colaboraban para corregir los fallos y mejorar los diseños. + +## Rollos sueltos + +Una busqueda en Freetube por "eurorack diy" parece arrojar resultado interesantes. diff --git a/content/posts/notes_general/notes_telar.md b/content/posts/notes_general/notes_telar.md new file mode 100644 index 0000000..e0d214d --- /dev/null +++ b/content/posts/notes_general/notes_telar.md @@ -0,0 +1,53 @@ +--- +weight: 4 +title: "Diseño y construcción de un telar" +date: 2021-08-12T11:33:13+0200 +draft: true +summary: "El telar" +categories: + - notes +tags: + - python + - programacion +--- + +{{< admonition type=warning title="Work in progress" open=true >}} + +No está completo. + +{{< /admonition >}} + +## Referencias + +* [Peines de telar cortados en aluminio](https://aitor-martin.blogspot.com/2014/07/peines-aluminio-telar-amona-micro-loom.html) +* [Blog actionweaver](https://actionweaver.wordpress.com/) el blog del autor del [telar Meinholf](https://actionweaver.wordpress.com/2010/03/31/collapsible-loom-building-plans-on-line-and-soon-in-print-in-oakland-ca/) + +https://www.textilesnaturales.com/es/tienda-online/libros-artesania/tejeduria-tradicional-en-galicia-el-telar-y-la-tecnica-por-samuel-ortiz-ma-jose-lois-albino-quiinteiro-jose-ortiz + +https://editorialcanela.gal/es/catalogo/sin-categorizar/tejeduria-tradicional-en-galicia-el-telar-y-la-tecnica/ + +https://tartan.galician.org/es/historia.htm#galicia + +https://lanaytelar.es/telares/ + +http://www.aceam.org/down/TELAR.pdf + +https://artesaniaeltelardebajolizo.blogspot.com/ + +https://historiaybiografias.com/telar_key/ + +https://repositorio.uam.es/bitstream/handle/10486/8514/45852_4.pdf?sequence=1 + +https://www.researchgate.net/publication/324756724_Desarrollo_de_un_prototipo_de_telar_plano_programable_para_tejido_artesanal + +https://angeltelar.blogspot.com/2008/09/especial-telar-maria-de-peine-o-mesa.html + +https://pre.museosdetenerife.org/assets/downloads/publication-f306689f7a.pdf + +https://www.mimecanicapopular.com/verhaga.php?n=124 + +https://www.geniolandia.com/13092723/cuales-son-las-partes-de-un-telar + + + + diff --git a/content/posts/notes_general/notes_thisblog.md b/content/posts/notes_general/notes_thisblog.md new file mode 100644 index 0000000..f0d9e9b --- /dev/null +++ b/content/posts/notes_general/notes_thisblog.md @@ -0,0 +1,406 @@ +--- +weight: 4 +title: "Migración de este blog a la última versión del tema FixIt" +date: 2024-01-12T17:30:10+0100 +draft: +summary: "Apuntes de la migración del blog al nuevo FixIt" +categories: +- notes +tags: +- hugo +- blog +- FixIt +--- + +## Intro + +Estos son los apuntes que he tomado cambiándo a la última versión del tema [_FixIt_](https://fixit.lruihao.cn/). + +Al cambiar la versión del tema he aprovechado para pulir algunos pequeños errores y de momento lo dejo en un sólo idioma. Nunca conseguí tiempo para hacer las traducciones de los artículos. + +## Instalación + +1. Nos creamos un repo en Github a partir del template que Lruihao (el autor del tema) ha publicado [aqui](https://github.com/hugo-fixit/hugo-fixit-starter1/blob/main/README.en.md). + +2. Clonamos este nuevo repo en nuestro disco. Me he hecho copias de este template tanto en [git.comacero.com](https://git.comacero.com) como en [mi gitlab](https://gitlab.com/salvari) + + ```bash + git clone --recursive https://github.com/salvari/newFixItgit + ``` + + Para actualizar el tema en el futuro: + + ```bash + git submodule update --remote --merge themes/FixIt + ``` + +3. Probamos el tema: + + ```bash + hsrv + # hsrv: aliased to hugo --buildDrafts server --disableFastRender + ``` + +4. Procedemos con la configuración + + +## Configuración + +Vamos a usar un directorio de configuración como [se describe](https://gohugo.io/getting-started/configuration/#configuration-directory) en la documentación de Hugo. + +### `config.toml` + +En el directorio de configuración tenemos la configuración organizada en distintos ficheros. El primero que necesitamos es `config.toml` que queda con el siguiente contenido: + +```toml {title="config.toml",hl_lines=["7-8","10-11"]} +# ------------------------------------------------------------------------------------- +# Hugo Configuration +# See: https://gohugo.io/getting-started/configuration/ +# ------------------------------------------------------------------------------------- + +# title = "Hugo FixIt Blog" +title = "comacero" +baseURL = "http://comacero.com/" +theme = "FixIt" +defaultContentLanguage = "es" +languageCode = "es" +hasCJKLanguage = true +enableRobotsTXT = true +enableEmoji = true +enableGitInfo = true +relativeURLs = false +buildDrafts = false +summaryLength = 150 +# ignore some build errors +ignoreErrors = ["error-remote-getjson", "error-missing-instagram-accesstoken"] +``` + +### `params.toml` + +A continuación configuramos el fichero `params.toml` + +En la primera sección del fichero cambiamos sólo tres líneas, configurando la descripción y el tema por defecto. Y ademas desativamos (de momento) la opción `gitRepo`. + +```toml{linenos=table,hl_lines=[9,13,15]} +# ------------------------------------------------------------------------------------- +# Theme Core Configuration +# See: https://fixit.lruihao.cn/theme-documentation-basics/#site-configuration +# ------------------------------------------------------------------------------------- + +# FixIt 0.2.15 | CHANGED FixIt theme version +version = "0.2.X" # e.g. "0.2.X", "0.2.15", "v0.2.15" etc. +# site description +description = "Notas personales sobre hardware y software libres" +# site keywords +keywords = ["Hugo", "FixIt"] +# site default theme ["light", "dark", "auto"] +defaultTheme = "dark" +# public git repo url only then enableGitInfo is true +# gitRepo = "" +# FixIt 0.1.1 | NEW which hash function used for SRI, when empty, no SRI is used +# ["sha256", "sha384", "sha512", "md5"] +fingerprint = "" +# FixIt 0.2.0 | NEW date format +dateFormat = "2006-01-02" +# website images for Open Graph and Twitter Cards +images = [] +# FixIt 0.2.12 | NEW enable PWA +enablePWA = false +# FixIt 0.2.14 | NEW whether to add external Icon for external links automatically +externalIcon = false +# FixIt 0.2.14 | NEW FixIt will, by default, inject a theme meta tag in the HTML head on the home page only. +# You can turn it off, but we would really appreciate if you don’t, as this is a good way to watch FixIt's popularity on the rise. +disableThemeInject = false + +``` + +En las secciones siguientes (`app` y `search`) del fichero `params.toml` no cambiamos nada de momento. Aunque probablemente probemos con otros motores de búsqueda en el futuro. + +```toml +# FixIt 0.2.0 | NEW App icon config +[app] + # optional site title override for the app when added to an iOS home screen or Android launcher + title = "" + # whether to omit favicon resource links + noFavicon = false + # modern SVG favicon to use in place of older style .png and .ico files + svgFavicon = "" + # Safari mask icon color + iconColor = "#5bbad5" + # Windows v8-10 tile color + tileColor = "#da532c" + # FixIt 0.2.12 | CHANGED Android browser theme color + [app.themeColor] + light = "#f8f8f8" + dark = "#252627" + +# FixIt 0.2.0 | NEW Search config +[search] + enable = true + # type of search engine ["lunr", "algolia", "fuse"] + type = "lunr" + # max index length of the chunked content + contentLength = 4000 + # placeholder of the search bar + placeholder = "test" + # FixIt 0.2.1 | NEW max number of results length + maxResultLength = 10 + # FixIt 0.2.3 | NEW snippet length of the result + snippetLength = 30 + # FixIt 0.2.1 | NEW HTML tag name of the highlight part in results + highlightTag = "em" + # FixIt 0.2.4 | NEW whether to use the absolute URL based on the baseURL in search index + absoluteURL = false + [search.algolia] + index = "" + appID = "" + searchKey = "" + [search.fuse] + # FixIt 0.2.17 | NEW https://fusejs.io/api/options.html + isCaseSensitive = false + minMatchCharLength = 2 + findAllMatches = false + location = 0 + threshold = 0.3 + distance = 100 + ignoreLocation = false + useExtendedSearch = false + ignoreFieldNorm = false +``` + +En la siguiente sección (`header`) si que hacemos cambios. Configuramos el logo, el nombre de la web. y el subtítulo que aparecerá en la cabecera. + +Para configurar el logo tendremos que añadir el fichero `cerito.svg` en el directorio `assets/images`. Tendremos que crear ese directorio y copiar el fichero `cerito.svg` en el mismo. + +```toml{linenos=table,linenostart=81,hl_lines=[10,12,22],} +# Header config +[header] + # FixIt 0.2.13 | CHANGED desktop header mode ["sticky", "normal", "auto"] + desktopMode = "sticky" + # FixIt 0.2.13 | CHANGED mobile header mode ["sticky", "normal", "auto"] + mobileMode = "auto" + # FixIt 0.2.0 | NEW Header title config + [header.title] + # URL of the LOGO + logo = "/images/cerito.svg" + # title name + name = "comacero" + # you can add extra information before the name (HTML format is supported), such as icons + pre = " " + # you can add extra information after the name (HTML format is supported), such as icons + post = "" + # FixIt 0.2.5 | NEW whether to use typeit animation for title name + typeit = false + # FixIt 0.2.12 | NEW Header subtitle config + [header.subtitle] + # subtitle name + name = "Notas sobre hardware y software libres" + # whether to use typeit animation for subtitle name + typeit = false + +# FixIt 0.2.18 | NEW Breadcrumb config +[breadcrumb] + enable = false + sticky = false + showHome = false +``` + + +En la sección `footer` cambiamos sólo el parámetro _site creation year_. + +```toml{linenos=table,linenostart=112,hl_lines=[14],} +# Footer config +[footer] + enable = true + # FixIt 0.2.17 | CHANGED Custom content (HTML format is supported) + # For advanced use, see parameter `params.customFilePath.footer` + custom = "" + # FixIt 0.2.0 | NEW whether to show Hugo and theme info + hugo = true + # FixIt 0.2.0 | NEW whether to show copyright info + copyright = true + # FixIt 0.2.0 | NEW whether to show the author + author = true + # Site creation year + since = 2019 + # FixIt 0.2.14 | NEW whether to show total word count of site content + wordCount = true + # FixIt 0.2.12 | NEW Public network security only in China (HTML format is supported) + gov = "" + # ICP info only in China (HTML format is supported) + icp = "" + # license info (HTML format is supported) + license = 'CC BY-NC-SA 4.0' + # FixIt 0.2.17 | CHANGED Site creation time + # FixIt 0.2.17 | CHANGED Site creation time + [footer.siteTime] + enable = false + animate = true + icon = "fa-solid fa-heartbeat" + pre = "" + value = "" # e.g. "2021-12-18T16:15:22+08:00" + # FixIt 0.2.17 | NEW footer lines order, optional values: ["first", 0, 1, 2, 3, 4, 5, "last"] + [footer.order] + powered = 0 + copyright = 0 + statistics = 0 + visitor = 0 + beian = 0 +``` + +En las secciones `section`, `list` y `tagcloud` no hacemos cambios de momento. + +```toml{linenos=table,linenostart=149} +# FixIt 0.2.0 | NEW Section (all posts) page config +[section] + # special amount of posts in each section page + paginate = 20 + # date format (month and day) + dateFormat = "01-02" + # amount of RSS pages + rss = 10 + # FixIt 0.2.13 | NEW recently updated posts settings + [section.recentlyUpdated] + enable = false + rss = false + days = 30 + maxCount = 10 + +# FixIt 0.2.0 | NEW List (category or tag) page config +[list] + # special amount of posts in each list page + paginate = 20 + # date format (month and day) + dateFormat = "01-02" + # amount of RSS pages + rss = 10 + +# FixIt 0.2.17 | NEW TagCloud config for tags page +[tagcloud] + enable = false + min = 14 # Minimum font size in px + max = 32 # Maximum font size in px + peakCount = 10 # Maximum count of posts per tag + orderby = "name" # Order of tags, optional values: ["name", "count"] +``` + +En la seccion `home` hacemos los cambios marcados en el listado. Es **importante** borrar el correo del _gravatar_ para que el tema cargue la imagen que le pasamos. + +```toml{linenos=table,linenostart=181,hl_lines=[9,11,17]} +# Home page config +[home] + # FixIt 0.2.0 | NEW amount of RSS pages + rss = 10 + # Home page profile + [home.profile] + enable = true + # Gravatar Email for preferred avatar in home page + gravatarEmail = "" + # URL of avatar shown in home page + avatarURL = "/images/cerito.svg" + # FixIt 0.2.17 | NEW identifier of avatar menu link + avatarMenu = "" + # FixIt 0.2.7 | CHANGED title shown in home page (HTML format is supported) + title = "" + # subtitle shown in home page + subtitle = "Apuntes personales de hardware y software libres" + # whether to use typeit animation for subtitle + typeit = true + # whether to show social links + social = true + # FixIt 0.2.0 | NEW disclaimer (HTML format is supported) + disclaimer = "Sólo para autoconsumo (^_^)" + # Home page posts + [home.posts] + enable = true + # special amount of posts in each home posts page + paginate = 6 +``` + +En la sección `social` puedes rellenar todas las RRSS que te apetezca que aparezcan en tu página. No voy a detallar todos los cambios pero pego la configuración para mastodon y gitea que, al menos en mi caso, es un poco especial: + +```toml +[social.Mastodon] + id = "@salvari" + prefix = "https://mastodon.gal/" + title = "Mastodon" +[social.gitea] + id = "salvari" + prefix = "https://git.comacero.com/" + title = "Gitea" +``` + +Por el momento no hacemos más cambios en el fichero `params.toml` + +### `menus.toml` + +Editamos el fichero y lo dejamos con los cambios marcados en el listado a continuación. + +```toml{linenos=table,hl_lines=[31,"47-66"]} +# ------------------------------------------------------------------------------------- +# Menu Configuration +# See: https://fixit.lruihao.cn/theme-documentation-basics/#menu-advanced-configuration +# ------------------------------------------------------------------------------------- + +[[main]] + identifier = "posts" + # you can add extra information before the name (HTML format is supported), such as icons + pre = "" + # you can add extra information after the name (HTML format is supported), such as icons + post = "" + name = "Posts" + url = "/posts/" + # title will be shown when you hover on this menu link + title = "" + weight = 1 + # FixIt 0.2.14 | NEW add user-defined content to menu items + [main.params] + # add css class to a specific menu item + class = "" + # whether set as a draft menu item whose function is similar to a draft post/page + draft = false + # FixIt 0.2.16 | NEW add fontawesome icon to a specific menu item + icon = "fa-solid fa-archive" + # FixIt 0.2.16 | NEW set menu item type, optional values: ["mobile", "desktop"] + type = "" +[[main]] + identifier = "categories" + pre = "" + post = "" + name = "Categorias" + url = "/categories/" + title = "" + weight = 2 + [main.params] + icon = "fa-solid fa-th" +[[main]] + identifier = "tags" + pre = "" + post = "" + name = "Etiquetas" + url = "/tags/" + title = "" + weight = 3 + [main.params] + icon = "fa-solid fa-tags" +[[main]] + identifier = "links" + pre = "" + post = "" + name = "Enlaces" + url = "/links/" + title = "" + weight = 6 + [main.params] + icon = "fa-solid fa-link" +[[main]] + identifier = "about" + pre = "" + post = "" + name = "Acerca de" + url = "/about/" + title = "" + weight = 10 + [main.params] + icon = "fa-solid fa-info-circle" +``` diff --git a/content/posts/notes_general/notes_traefik.md b/content/posts/notes_general/notes_traefik.md new file mode 100644 index 0000000..8b35ecf --- /dev/null +++ b/content/posts/notes_general/notes_traefik.md @@ -0,0 +1,1152 @@ +--- +weight: 4 +title: "Apuntes de Traefik v2" +date: 2021-01-31T22:28:31+0100 +draft: false +summary: "Apuntes de Traefik v2" +categories: + - notes +tags: + - docker + - traefik +--- + +Apuntes sobre Traefik v2. + + + +# Traefik + +{{< admonition type=abstract title="Referencias" state=open >}} + +Estas notas sobre _Traefik_ son un refrito de las siguientes fuentes: +- [Traefik - Quick Start](https://doc.traefik.io/traefik/getting-started/quick-start/) +- [Joshua Avalon Blog: Setup Traefik v2](https://joshuaavalon.io/setup-traefik-v2-step-by-step) +- [Digital Ocean: Howto use Traefik as reverse proxy for Docker](https://www.digitalocean.com/community/tutorials/how-to-use-traefik-v2-as-a-reverse-proxy-for-docker-containers-on-ubuntu-20-04) +- [Hash with openssl](https://blog.roberthallam.org/2020/05/generating-a-traefik-nginx-password-hash-without-htpasswd/) +- [Containeroo: Traefik 2.0 + Docker — a Simple Step by Step Guide](https://medium.com/@containeroo/traefik-2-0-docker-a-simple-step-by-step-guide-e0be0c17cfa5) +- [Containeroo: Traefik 2.0 + Docker — an Advanced Guide](https://medium.com/@containeroo/traefik-2-0-docker-an-advanced-guide-d098b9e9be96) +- [Containeroo: Traefik 2.2 + Docker: Entry Point Configuration](https://medium.com/@containeroo/traefik-2-2-docker-global-entrypoint-configuration-ff11d7f84913) + +{{< /admonition >}} + + + + +## _Edge Router_ + +_Traefik_ es un _edge router_ (router de borde, router frontera, proxy +inverso) Es decir que es el punto de entrada a tu servidor (o +plataforma) y se encarga de interceptar cada petición de entrada y de +enrutarlas a los servicios correspondientes. Puede hacer el +enrutamiento basándose en muchos criterios diferentes (nombre del +host, cabeceras, etc.) + +{{< admonition type=tip title="¿Para qué sirve?" state=open >}} +Lo normal es que instales _Traefik_ entre tu platafoma de servicios e +Internet. De esta forma no tienes que abrir mas que uno o dos puertos +hacia la red (tipicamente el 80 y el 443) y atender todas las +peticiones a los distintos servicios a través de estos dos puertos. + +**Plataforma de servicios** equivale a un VPS, a una raspi en tu casa, a +un servidor dedicado, etc. etc. + +{{< /admonition >}} + +## _auto-discovery_ de servicios + +Normalmente los _edge_router_ o proxy inversos necesitan un fichero de +configuración detallando las reglas para enrutar las peticiones a los +servicios. _Traefik_ obtiene las reglas de los propios servicios. Así +que en el momento de desplegar un nuevo servicio, un contenedor +_Docker_ en lo que respecta a este documento, especificaremos la +información que ese servicio debe dar a _Traefik_ para establecer los +enrutamientos. + +De esta forma cuando se despliega un servicio, _Traefik_ lo detecta y +actualiza las reglas en tiempo real. De la misma forma, si el servicio +se cae o se para, _Traefik_ elimina las rutas asociadas en tiempo +real. + +Para descubrir los servicios _Traefik_ usa el API de la plataforma que +los alberga, en el lenguaje propio de _Traefik_ usa un ___Provider___. +Como nosotros vamos a usar _Traefik_ en _Docker_ vamos a centrarnos en +el _docker provider_. + +## Ejemplo01: Uno facilito + +El siguiente fichero _docker-compose_ contiene una configuración lo +más sencilla posible con el __docker provider_. + +Preparamos los ficheros y directorios de trabajo: + +```bash +mkdir -p ~/work/docker/ejemplo_01/traefik +touch ~/work/docker/ejemplo_01/traefik/docker-compose.yml +``` + +El contenido del fichero `~/work/docker/ejemplo01/traefik/docker-compose.yml` +sería: + +```yml +version: '3' + +services: + reverse-proxy: + # The official v2 Traefik docker image + image: traefik + # Enables the web UI and tells Traefik to listen to docker + command: --api.insecure=true --providers.docker + ports: + # The HTTP port + - "80:80" + # The Web UI (enabled by --api.insecure=true) + - "8080:8080" + volumes: + # So that Traefik can listen to the Docker events + - /var/run/docker.sock:/var/run/docker.sock +``` + +Este fichero _docker-compose_: +- Define el servicio _reverse-proxy_ + - Basado en la imagen `traefik:latest` + - pasamos las opciones `--api.insecure=true` y + `--providers.docker` al comando de arranque de la imagen + _Traefik_ + - Conectamos los puertos 80 y 8080 del contenedor al exterior (a la red host) + - Definimos un _bind mount_ para que el socket de comunicación con + _Docker_ esté disponible para el contenedor. Lo necesita para + que _Traefik_ pueda conectar el _provider docker_ al API de + _Docker_ en la máquina _host_ + +{{< admonition type=danger title="API Insecure" open=true >}} + +¡Cuidado! la opción `api.insecure` __no debe__ usarse nunca en +producción. Con esta opción estamos dejando el acceso al API de +nuestro _Traefik_ abierto y sin protección. + +Más adelante veremos opciones para configurar de forma segura el +acceso al API. + +{{< /admonition >}} + +En cuanto lanzemos nuestro _docker-compose_: + +`docker-compose --verbose up -d` + +Podremos acceder al _dashboard_ de _Traefik_ visitando +, donde `my_server_ip` será la dirección del +server donde has lanzado el contenedor o `localhost` si estás haciendo +las pruebas en tu ordenador personal. + +{{< admonition type=tip title="firewall" open=true >}} + +Si estás lanzando el contenedor en un servidor verás que el propio +_Docker_ se encarga de abrir el _firewall_ añadiendo las rutas +necesarias a las _iptables_. + +Puedes comprobar las reglas iptables para el puerto 8080 con el +comando `iptables-save | grep 8080` + +[Más info](https://docs.docker.com/network/iptables/) + +{{< /admonition >}} + +Con este _Traefik_ básico que hemos lanzado, ya prodríamos lanzar +servicios que conectaran con el mismo. Vamos a añadir unas lineas a +nuestro fichero _docker-compose_, que quedaría así: + +```yml +version: '3' + +services: + reverse-proxy: + # The official v2 Traefik docker image + image: traefik + # Enables the web UI and tells Traefik to listen to docker + command: --api.insecure=true --providers.docker + ports: + # The HTTP port + - "80:80" + # The Web UI (enabled by --api.insecure=true) + - "8080:8080" + volumes: + # So that Traefik can listen to the Docker events + - /var/run/docker.sock:/var/run/docker.sock + # ... + whoami: + # A container that exposes an API to show its IP address + image: traefik/whoami + labels: + - "traefik.http.routers.whoami.rule=Host(`whoami.docker.localhost`)" +``` + +Hemos incluido un nuevo servicio _whoami_ en nuestro fichero +`docker-compose.yml`. Es un servicio bastante chorras que nos facilita +la gente de _Traefik_ para hacer pruebas. Simplemente responde +peticiones con algunos datos del servidor `whoami` como la dirección +IP o el nombre de la máquina. En este caso responderá con datos de la +"máquina virtual", es decir el contenedor `whoami` dentro de _Docker_. + +Si levantamos el nuevo servicio con `docker-compose up -d whoami` +podremos consultar el dashboard de _Traefik_ + o directamente el _raw api_ + y comprobaremos que _Traefik_ ha +detectado el nuevo servicio `whoami` y ha configurado la ruta. + +Si queremos comprobarlo en la práctica podemos ejecutar una _request_ +con _curl_, con el comando: `curl -H Host:whoami.docker.localhost http://my_server_ip` + +```bash +curl -H Host:whoami.docker.localhost http://my_server_ip +Hostname: 0e1584c3151f +IP: 127.0.0.1 +IP: 172.19.0.3 +RemoteAddr: 172.19.0.2:55008 +GET / HTTP/1.1 +Host: whoami.docker.localhost +User-Agent: curl/7.68.0 +Accept: */* +Accept-Encoding: gzip +X-Forwarded-For: 192.168.0.154 +X-Forwarded-Host: whoami.docker.localhost +X-Forwarded-Port: 80 +X-Forwarded-Proto: http +X-Forwarded-Server: 3b4d7b2f09be +X-Real-Ip: 192.168.0.154 +``` + +**¿Qué esta pasando aquí?** + +_Traefik_ abre dos _entry points_ uno atiende peticiones en el puerto +80 y otro en el 8080 (para acceder al _dashboard_). + +Cuando lanzamos nuestra petición con `curl` _Traefik_ la recibe por el +puerto 80, comprueba que hay un _router_ que encaja con la cabecera +(`whoami.docker.localhost`) y, de acuerdo con esa regla, le pasa la +petición al contenedor del servicio `whoami`. + +Este _router_ se configuró automáticamente gracias a la etiqueta +`traefik.http.routers.whoami.rule=Host('whoami.docker.localhost')` que +asociamos al contenedor `whoami` en el fichero `docker-compose.yml`. +Estas etiquetas son procesadas a través del _provider_ Docker para +reconfigurar _Traefik_ en tiempo real. + +El servicio `whoami` atiende la petición y devuelve la respuesta al +usuario. + +{{< admonition type=warning title="localhost vs my_server_ip" state=open >}} + +Yo estoy probando _Traefik_ en un servidor en mi intranet, en mi +caso un mini-pc, en tu caso podría ser una raspi, o podrías estar +haciendo las pruebas en un vps. O también podrías estar haciendo las +pruebas en tu propio ordenador. + +Si estás haciendo las pruebas en tu propio ordenador, tienes que usar +`localhost` o `127.0.0.1` en todos los sitios donde he dicho +`my_server_ip`, si estás usando un servidor de cualquier tipo, pues la +ip correspondiente. + +El caso es que hemos declarado la ruta para _Traefik_ como si +estuvieramos en nuestro ordenador (_localhost_), así que para hacer la +prueba rápida con _curl_, especificamos la cabecera de nuestra request +como `whoami.docker.localhost` aunque después __encaminamos__ la _request_ a +nuestro servidor con la URL `http://my_server_ip` + +{{< /admonition >}} + +Con esta configuración tan sencilla podemos ver otra funcionalidad muy +importante de _Traefik_, si lanzamos un segundo servidor whoami (un +segundo contenedor) con el comando: + +`docker-compose up -d --scale whoami=2` + +_Traefik_ se encargará de hacer __reparto de carga__ entre los dos +servidores. Si lanzamos el comando _curl_ anterior, veremos como nos +van respondiendo los dos contenedores. + +Si quieres parar los contenedores ejecuta `docker-compose down` + +## Conceptos centrales en _Traefik_ + +### _Providers_ + +Son los agentes capaces de cambiar la configuración dinámica de +_Traefik_ hay varios tipos: _file_, _Kubernetes_, _Docker_ etc. etc. + +En nuestro caso nos vamos a centrar en el _docker provider_ + +**Los _providers_ son parte de la configuración estática de _Traefik_.** + +### Configuraciones de _Traefik_ + +En _Traefik_ distinguimos entre dos configuraciónes: + +___static configuration___ + +: La configuración de arranque de _Traefik_, aquí se definen los + ___Providers__ (_Docker_ en nuestro ejemplo anterior) y los ___entry + points___ (los puertos 80 y 8080 en nuestro ejemplo anterior). Es + decir, elementos que no cambian muy a menudo a lo largo de la vida + del _edge router_ + + _Traefik_ lee su configuración estática de tres posibles orígenes + (mutuamente exclusivos): + 1. [Fichero de configuración](https://doc.traefik.io/traefik/reference/static-configuration/file/) + 2. [Parámetros por linea de comandos](https://doc.traefik.io/traefik/reference/static-configuration/cli/) + 3. [En variables de entorno](https://doc.traefik.io/traefik/reference/static-configuration/env/) + +___dynamic configuration___ + +: La configuración dinámica del enrutado. Comprende toda la definición + del procesado de las _request_ recibidas por el router. Esta + configuración puede cambiar y se recarga en caliente sin parar el + router y sin provocar pérdidas de conexión o interrupción del + servicio. + + _Traefik_ obtiene su configuración dinámica a través de los + __Providers__. En el ejemplo anterior _Docker_ informa a _Traefik_ + de las rutas que necesita cada servicio gracias a las etiquetas + ([_Labels_](https://docs.docker.com/engine/reference/commandline/run/#set-metadata-on-container--l---label---label-file)) + que añadimos en la configuración (del servicio) (ver + [referencia](https://doc.traefik.io/traefik/providers/docker/)). Es + un caso de configuración dinámica de _Traefik_ a traves del _Docker + Provider_. + + +### _Entry Point_ + +Son los componentes principales del _"frontend"_ de nuestro _edge +router_. Básicamente son los puertos de entrada donde se reciben las +peticiones de servicio del "exterior". + +**Los _entry point_ son parte de la configuración estática.** + +{{< admonition type=tip title="Traefik requests" state=open >}} + +_Traefik_ puede atender peticiones _HTTP_ o peticiones _TCP_ por qué +puede actuar como un _router_ de nivel 7 (_HTTP_) o de nivel 4 +(_TCP_). + +{{< /admonition >}} + +### _Services_ + +Serían los componentes del _"backend""_, en nuestro caso serán +servicios implementados en contenedores Docker. + +**Los _services_ son parte de la configuración dinámica.** + +### _Routers_ + +Son las reglas para pasar peticiones desde el _"frontend"_ al _"backend"_. +Se implementan y reconfiguran por la información recibida por los +_providers_. + +**Los _routers_ son parte de la configuración dinámica.** + +### _Middlewares_ + +Pueden hacer cambios en las peticiones o en las respuestas a +peticiones. Hay multitud de _middlewares_ y además se pueden encadenar +para acumular cambios. + +**Los _middlewares_ son parte de la configuración dinámica.** + + +## Ejemplo02: Certificados SSL con _Let's Encrypt_ + +Vamos con un ejemplo un pelín más complejo. Vamos a definir un +contenedor de _Traefik_ que simplemente expone su _Dashboard_, de +momento no vamos a definir más contenedores, pero podremos añadirlos a +este escenario en el futuro. + +La gracia del ejemplo es que _Traefik_ obtendrá los certificados SSL +del dominio (p.ej. miDominio.com) automáticamente desde [_Let's +Encrypt_](https://letsencrypt.org/). Y expondrá el _dashboard_ en un +subdominio, digamos en . + +Preparamos los directorios y los ficheros de trabajo. + +```bash +mkdir -p ~/work/docker/ejemplo_02/traefik +touch ~/work/docker/ejemplo_02/traefik/{traefik.yml,traefik_dynamic.yml,acme.json} +chmod 600 ~/work/docker/ejemplo_02/traefik/acme.json +``` + +- `traefik.yml` será el fichero de configuración estática de _Traefik_. +- `traefik_dynamic.yml` será el fichero de configuración dinámica de _Traefik_ +- `acme.json` es el fichero que almacenará los certificados HTTPS + +### Configuración estática de _Traefik_ + +Primero vamos a definir la configuración estática de nuestro +_Traefik_, es imprescindible tener una. La almacenaremos en el fichero +`traefik.yml` y más adelante tendremos que hacer que el contenedor +_Traefik_ pueda acceder a esa configuración (con un _bind mount_ por +ejemplo) + +Dentro de esta configuración definiremos: + +___entry points___ + +: Definiremos dos _entry points_: `http` y `https` que aceptarán + peticiones en los puertos 80 y 443. + + Los nombres de los _entry points_ son etiquetas arbitrarias, puedes + llamarlos como quieras. + + ```traefik.yml + entryPoints: + http: + address: :80 + https: + address: :443 + ``` + + La redirección de puertos se solía hacer con un _middleware_ pero + desde la version 2.2 de _Traefik_ podemos definirla en la + configuración estática añadiendo unas lineas en la propia definición + de los _entry points_: + + ```traefik.yml + entryPoints: + http: + address: :80 + http: + redirections: + entrypoint: + to: https + https: + address: :443 + + +__providers___ + +: Vamos a definir dos _providers_ diferentes, uno de tipo `docker` y + otro de tipo `file`. + + Nuestro objetivo es usar _Traefik_ en Docker así que siempre deberíamos + definir el _provider_ docker. Recordemos que a través de este + _provider_ los contenedores (servicios) que definamos en nuestro + _Docker_ informarán a _Traefik_ de que rutas necesitan para recibir + peticiones. + + ```traefik.yml + entryPoints: + http: + address: :80 + http: + redirections: + entrypoint: + to: https + https: + address: :443 + + providers: + docker: + endpoint: unix:///var/run/docker.sock + exposedByDefault: false + file: + filename: traefik_dynamic.yml + ``` + + El _docker provider_ de _Traefik_ se comunica con _Docker_ (en la + máquina _host_) a traves del _socket_ de _Docker_ (en el sistema de + ficheros de la máquina _host). Así que necesitaremos que el + contenedor _Docker_ pueda acceder al _socket_ con otro _bind mount_ + + El parámetro `exposedByDefault` por defecto vale `true` y hace que + todos los contenedores en _Docker_ sean visibles para _Traefik_. El + valor recomendado es justo el contrario: `false`, para que _Traefik_ + "vea" únicamente los contenedores que tiene que tiene que enrutar. + + {{< admonition type=warning >}} + + En este ejemplo concreto el `docker provider` no hara nada. Pero + como estamos viendo como usar _Traefik_ con _Docker_ es mejor + acostumbrarse a definirlo siempre. En cuanto definamos algún + contenedor nos hará falta. + + {{< /admonition >}} + + Además del _provider docker_ definimos un segundo _provider_ de tipo + _file_. Este segundo _provider_ nos permitirá especificar la + configuración dinámica mediante un fichero. En el futuro la + configuración dinámica podría ampliarse a través del _docker + provider_ si definimos nuevos contenedores. + + {{< admonition type=info title="provider file: dirname vs filename" state=open >}} + + Podemos definir un _provider file_ especificando un `dirname` en + lugar de un `filename`. En ese caso podríamos tener varios ficheros + en el directorio especificado para añadir modificaciones a la + configuración dinámica de _Traefik_ + + {{< /admonition >}} + +__certificates resolvers__ + +: Se encargará de negociar los certificados SSL con _Let's Encrypt_ + ¡automáticamente! + + Los _certificates resolvers_ son parte de la configuración estática. + + Hay [varias maneras](https://doc.traefik.io/traefik/v2.0/https/acme/) + de que _Traefik_ obtenga los certificados, en este ejemplo usamos + una muy simple, el _http challenge_. + + ```traefik.yml + entryPoints: + http: + address: :80 + http: + redirections: + entrypoint: + to: https + https: + address: :443 + + providers: + docker: + endpoint: unix:///var/run/docker.sock + exposedByDefault: false + file: + filename: traefik_dynamic.yml + + certificatesResolvers: + myresolver: + acme: + email: + storage: acme.json + httpChallenge: + entryPoint: http + ``` + + Las lineas añadidas al fichero `traefik.yml` definen un + _certificates resolver_ llamado `myresolver` (es una etiqueta, + puedes llamarlo como quieras). Tenemos que especificar un correo + electrónico (sólo a efectos de notificaciones) y el fichero (o la + ruta) donde _Traefik_ almacenará los certificados obtenidos. Además + para el _httpChallenge_ tenemos que usar obligatoriamente el puerto + 80, así que especificamos `entryPoint: http` que previamente + asociamos a ese puerto. + + _Traefik_ no va a pedir certificados de dominio por iniciativa + propia, tendremos que indicar, en la configuración dinámica, que + servicios queremos asegurar con un certificado (en principio todos) + + Tambien tenemos que acordarnos de que más adelante tendremos que + crear un _bind mount_ para tener fácil acceso al fichero + `acme.json`. + + {{< admonition type=tip title="Protocolo ACME" state=open >}} + + El agente ACME de _Traefik_ genera una pareja de claves + pública-privada para hablar con la autoridad certificadora (CA) de + _Let's Encrypt_. Después "pregunta" a la CA que debe hacer para + probar que controla un dominio determinado. + + La CA le planteará un conjunto de posibles "desafíos". Puede pedirle + por ejemplo, que cree un subdominio determinado en el dominio + reclamado, o servir un contenido arbitrario en una dirección HTTP + del dominio. Junto con los desafíos la CA envia un _nonce_ (un + número de un sólo uso) + + Si el agente ACME consigue completar alguno de los desafíos + planteados. Informa a la CA y le devuelve el _nonce_ firmado con su + clave. + + La CA comprueba la solución del desafío y que la firma del _nonce_ + son válidas. A partir de este punto la pareja de claves asociada al + dominio se considera autorizada, y el agente ACME ya puede solicitar + certificados y/o revocarlos usando esta pareja de claves. + + {{< /admonition >}} + +___dashboard___ + +: Activamos el _dashboard_. Necesitamos el _dashboard_ activo. Más + adelante cuando definamos la configuración dinámica indicaremos en + que URL podemos acceder al _dashboard_ y le añadiremos seguridad + para que no sea público. + + ```traefik.yml + entryPoints: + http: + address: :80 + http: + redirections: + entrypoint: + to: https + https: + address: :443 + + providers: + docker: + endpoint: unix:///var/run/docker.sock + exposedByDefault: false + file: + filename: traefik_dynamic.yml + + certificatesResolvers: + myresolver: + acme: + email: + storage: acme.json + httpChallenge: + entryPoint: http + + api: + dashboard: true + ``` + +Con esto tendríamos completa la configuración estática de nuestro +_Traefik_. Podríamos incluso arrancar nuestro contenedor _Traefik_ en +_Docker_, teniendo cuidado de mapear los puertos 80 y 443 al host y +estableciendo los _bind mounts_ necesarios para que el contenedor lea +el fichero de configuración estática y el almacen de claves +`acme.json`. Por ejemplo podríamos ejecutar: + +```bash + docker run -d \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v $PWD/traefik.yml:/traefik.yml \ + -v $PWD/acme.json:/acme.json \ + -p 80:80 \ + -p 443:443 \ + --name traefik \ + traefik:v2.4.0 +``` + +Claro que la configuración dinámica estaría vacía, así que nuestro +_Traefik_ sería bastante tonto. Expondría los puertos 80 y 443, pero +no sabría que hacer con las peticiones que le llegaran a esos puertos. +Además, aunque levantaría el _dashboard_ y su acceso en el puerto 8080 +del contenedor, este no sería accesible. Por un lado no hemos añadido +la opción `insecure: true` y por otro no hemos establecido usuario y +password así que no hay manera de consultar el `dashboard` (todavía) + +### Configuración dinámica de _Traefik_ + +Vamos a completar la configuración de nuestro _edge router_ definiendo +la parte dinámica de la misma. + +La vamos a definir en un fichero, pero igual que la estática no hay +por que hacerlo así. + +Ya hemos dicho que nuestro router básico solo expondrá el _dashboard_, +así que tenemos que definir la capa de seguridad de acceso al +_dashboard_ y una ruta apuntando al servicio. + +Para usar el _middleware_ [_Basic +Auth_](https://doc.traefik.io/traefik/middlewares/basicauth/), +necesitamos declarar parejas de usurio:password. Las contraseñas deben +ser _hash_ MD5, SHA1 o BCrypt. + +Para generar las credenciales, es decir la pareja de usuario y +password "_hasheada_" podemos usar distintas herramientas. + +Con `htpasswd`: + +```bash +sudo apt install apache-utils +echo $(htpasswd -nb ) +``` + +Con `openssl`: + +```bash +openssl passwd -apr1 +``` + +{{< admonition type=info title="Hashes" state=open >}} + +Si ejecutas varias veces el comando `openssl` con la misma contraseña +los resultados serán **distintos**. + +Esto es normal, en cada ejecución se cambia el `salt` para que +funcione así. Esto impide, por ejemplo, que un atacante detecte si +estas reutilizando contraseñas. + +Si te fijas en la salida tiene tres campos delimitados +por `$$` + +El primer campo es `apr1` el tipo de _hash_ que estamos usando. + +El segundo campo es el `salt` (semilla) utilizado para generar el _hash_ + +El tercer campo es el _hash_ propiamente dicho + +Por otro lado veréis que en muchos sitios de internet indican que hay que duplicar +los símbolos '$' para escaparlos, tipicamente usando algún filtro +_sed_ como `| sed -E "s:[\$]:\$\$:g"` pero **solo aplica** cuando +usamos _labels_ para configurar los servicios, **no en nuestro caso** +ya que usamos un fichero la configuración dinámica. + +{{< /admonition >}} + +Una vez que tenemos nuestra contraseña super segura en formato _hash_ +podemos añadir las siguientes lineas a nuestra configuración dinámica, +para definir un _middleware_ que llamaremos `myauth`: + +```yaml +# Declaring the user list +http: + middlewares: + myauth: + basicAuth: + users: + - "user1:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/" + - "user2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0" +``` + + +Una vez definido nuestro _middleware_ de tipo _basicAuth_ solo nos +falta definir el +[_router_](https://doc.traefik.io/traefik/routing/routers/) para +nuestro _dashboard_. Lo llamaremos `mydash`: + +```yaml +http: + middlewares: + myauth: + basicAuth: + users: + - "user1:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/" + - "user2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0" + + routers: + mydash: + rule: Host(`dashtraefik.miDominio.com`) + middlewares: + - myauth + service: api@internal # This is the defined name for api. You cannot change it. + tls: + certresolver: myresolver +``` + +Con esto completamos la configuración de nuestro _Traefik_, antes de +poder probar tendríamos que asegurarnos de apuntar el nombre +`dashtraefik.miDominio.com` a la dirección IP correcta en nuestro gestor del dominio. + +Si ya tenemos el DNS correctamente configurado podríamos lanzar la +prueba con: + +```bash + docker run -d \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v $PWD/traefik.yml:/traefik.yml \ + -v $PWD/traefik_dynamic.yml:/traefik_dynamic.yml \ + -v $PWD/acme.json:/acme.json \ + -p 80:80 \ + -p 443:443 \ + --name traefik \ + traefik:v2.4.0 +``` + +Si no hay errores en nuestro DNS ni en la configuración del _Traefik_ +comprobaremos que podemos acceder tanto a + como + (el primero estará redirigido al segundo) + +Veremos también que _Traefik_ ha negociado con _Let's Encrypt_ para +obtener los certificados SSL para nuestro dominio. Prueba a hacer un +`cat acme.json` en el servidor para ver el contenido del fichero. + +En caso de problemas, probablemente el primer paso sea comprobar los +logs del contenedor _Traefik_ con `docker logs traefik` + +## Ejemplo03: Una configuración sencilla para empezar con Traefik en Producción + +Vamos a ver una configuración sencilla que nos sirva de base para +empezar en producción. + +Posiblemente quieras crear un usuario específico para mantener el +_docker_ en tu servidor. + +```bash +sudo adduser dockadmin +gpasswd -a dockadmin sudo +gpasswd -a dockadmin docker +``` + +Preparamos los directorios y ficheros de trabajo: + +```bash +mkdir -p ~/work/docker/ejemplo_03/{portainer,traefik/{configurations,letsencrypt}} +touch ~/work/docker/ejemplo_03/{docker-compose.yml,traefik/{traefik.yml,letsencrypt/acme.json,configurations/middlewares.yml}} +chmod 600 ~/work/docker/ejemplo03/traefik/letsencrypt/acme.json +``` + +### _Networks_ + +Vamos a organizar nuestro _Docker_ con dos _networks_: + +___frontend___ + +: En la red _frontend_ tendremos todos los contenedores que expongan + un interfaz de usuario "al exterior". Evidentemente nuestro + contenedor _Traefik_ tiene que estar en esta red. Otro ejemplo + sería un servidor web. En realidad todos los contenedores que van + implementar servicios a través de _Traefik_ tienen que estar + conectados a esta red. (Podríamos haberla llamado _traefik_) + +___backend___ + +: Los contenedores que no exponen servicios al exterior solo tienen + que estar en esta red. Por ejemplo un servidor de base de datos es + muy posible que esté conectado exclusivamente a la red _backend_. + + Los contenedores de la red _frontend_ pueden necesitar una conexión + adicional con la red _backend_. Por ejemplo un contenedor + _Wordpress_ tiene que estar en la red _frontend_ para publicar las + páginas web a través de _Traefik_ pero también en la red _backend_ + para comunicarse con el servidor de base de datos. + +{{< image src="/images/traefik/traefik_networks.png" >}} + +Creamos las redes en _Docker_ con: + +```bash + docker network create --subnet 172.20.0.0/16 backend + docker network create --subnet 172.21.0.0/16 frontend +``` + +Los rangos de direcciones IP son arbitrarios, igual que los nombres +(_frontend_ y _backend_) de las redes. Podríamos usar otros cualquiera. + +{{< admonition type=warning title="Traefik V1: frontend y backend" state=open >}} + +**Ojo** en _Traefik V1_ los términos _frontend_ y _backend_ tienen un significado especial. Nada que ver con las redes definidas en el ejemplo03. Cuando consultes información en internet **asegúrate** de que referencian la versión actual de _Traefik_ (2.4 cuando escribo esto). Si hablan mucho de definir el _frontend_ y el _backend_ es muy posible que estén hablando de _Traefik V1_ + +{{< /admonition >}} + +### _Traefik_ + +Vamos a especificar la configuración de _Traefik_: + +* Tendremos la configuración estática en el fichero `traefik.yml` +* Definiremos un directorio `configurations` mediante un _provider + file_, todos los ficheros que pongamos en ese directorio se cargarán + como parte de la configuración dinámica. Cualquier modificación en + los ficheros o cualquier nuevo fichero se cargará "al vuelo", sin + necesidad de reiniciar _Traefik_. + +#### _middlewares_ comunes + +En el fichero `/configurations/middlewares.yml` vamos a definir +_middlewares_ que usaremos con muchos (o todos) nuestros contenedores. + +Ya sabemos que en _Traefik_ podemos definir la configuración dinámica +de muchas formas, podríamos usar _labels_ asociadas al contenedor +_Traefik_ para definir estos _middlewares_, pero creo que así queda más +ordenado. + +Definimos los _middlewares_: + +___secureHeaders___ + +: Que define una serie de cabeceras que vamos a aplicar a todas las + conexiones HTTP de nuestro _Traefik_. + * ___frameDeny: true___ Para evitar ataques + [_click-jacking_](https://developer.mozilla.org/en-US/docs/Web/Security/Types_of_attacks#click-jacking) + * ___sslRedirect: true___ Para permitir solo peticiones _https_ + * ___browserXssFilter___ Para paliar ataques de [_cross site + scripting_](https://developer.mozilla.org/en-US/docs/Web/Security/Types_of_attacks#cross-site_scripting_xss) + * ___contentTypeNosniff: true___ Ver + [referencia](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options) + * ___forceSTSHeader: true___: fuerza cabeceras STS para todas las + conexiones, con el flag _preload_ y la directiva + _includeSubdomains_ y un _max-age_ de un año. + +___local-whitelist___ + +: Los servicios a los que apliquemos este _middleware_ sólo se podrán + acceder desde IP en la _whitelist_. Este _middleware_ es + interesante si accedes a los servicios desde la intranet. + +___user-auth___ + +: Para definir credenciales de acceso a nuestros servicios con + _basicAuth_. _Traefik_ nos pedirá usuario y contraseña para los + servicios a los que apliquemos este _middleware_. + +___localsec___ + +: Este es un ejemplo de "cadena de _middlewares_", aplicar este + _middleware_ equivale a aplicar _secureHeaders_ y + _local-whitelist_. ___chain___ es muy interesante para organizar + nuestros _middlewares_ y aplicar la misma configuración a varios + contenedores. + +Contenido del fichero `middlewares.yml`: + +```yaml +http: + middlewares: + secureHeaders: + headers: + frameDeny: true + sslRedirect: true + forceSTSHeader: true + stsIncludeSubdomains: true + stsPreload: true + stsSeconds: 31536000 + + local-whitelist: + ipWhiteList: + sourceRange: + - "10.0.0.0/24" + - "192.168.0.0/16" + - "172.0.0.0/8" + + user-auth: + basicAuth: + users: + - "user1:$apr1$MTqfVwiE$FKkzT5ERGFqwH9f3uipxA1" + - "user2:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/" + - "user3:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0" + localsec: + chain: + middlewares: + - local-whitelist + - secureHeaders +``` + +#### Configuración estática + +En el fichero `traefik.yml` definimos la configuración estática de _Traefik_: + +* Activamos el _dashboard_ +* Definimos los _entry points_ + * `htpp` redirigido a `https` + * `https` al que aplicamos los _middleware_ con las cabeceras de + seguridad y el _certresolver_ apuntando a _Let's Encrypt_ para + generar certificados SSL +* Definimos los _providers_ + * `docker` imprescindible claro + * `file` apuntando al directorio `/configurations` para cargar + ficheros que amplien la configuración dinámica +* Definimos el _certresolver_ para obtener los certificados SSL + automáticamente desde _Let's Encrypt_ + +El contenido del fichero `traefik.yml` nos quedaría: + +```yaml +# traefik.yml +api: + dashboard: true + +entryPoints: + http: + address: :80 + http: + redirections: + entryPoint: + to: https + + https: + address: :443 + http: + middlewares: + - secureHeaders@file + tls: + certResolver: letsencrypt + +providers: + docker: + endpoint: "unix:///var/run/docker.sock" + exposedByDefault: false + file: + directory: /configurations + +certificatesResolvers: + letsencrypt: + acme: + email: youruser@mailprovider.com + storage: acme.json + httpChallenge: + entryPoint: http +``` + +### docker-compose.yml + +Ahora que ya tenemos listos los ficheros de configuración de _Traefik_ +vamos a definir el fichero `docker-compose.yml`: +* Definimos siempre la versión de nuestras imágenes _Docker_ + explicitamente. Es muy mala idea usar `:latest` en producción, hay + que saber que versión usamos y hacer las actualizaciones con + conocimiento de causa. +* Definimos un nombre (`traefik`) para nuestro contenedor, y definimos + una política para `restarts` +* Establecemos la opción `security_opt: no-new-privileges` para + impedir escaladas de privilegios con `setuid` +* Definimos los _volumes_ del contenedor _Traefik_ + * Mapeamos `/etc/localtime` para que el contenedor sincronice la + hora con el _host_ + * Mapeamos el _socket_ de _Docker_, imprescindible para el + _provider docker_ + * Mapeamos el fichero de configuración estática `traefik,yml`, el + directorio de configuraciones dinámicas `/configurations` y el + fichero `acme.json` para el _certresolver_ +* Añadimos etiquetas (_labels_) para la configuración dinámica del + contenedor _Traefik_ (concretamente del _dashboard_): + * El servicio (se refiere al _dashboard_)se proveerá a través de _Traefik_ + * El contenedor se conecta a la red _frontend_ + * Se configura el router `traefik-secure` + * Para el servicio _Traefik dashboard_(`api@internal`) + * Con _middleware_ `user-auth` + * Con la regla ``Host(`dashtraefik.yourdomain.com`)`` +* Por último declaramos la red (_network_) _frontend_ como `external` + ya que la hemos creado previamente. + + +```yaml +version: '3' + +services: + traefik: + image: traefik:v2.4 + container_name: traefik + restart: unless-stopped + security_opt: + - no-new-privileges:true + networks: + - frontend + ports: + - 80:80 + - 443:443 + volumes: + - /etc/localtime:/etc/localtime:ro + - /var/run/docker.sock:/var/run/docker.sock:ro + - ./traefik/traefik.yml:/traefik.yml:ro + - ./traefik/configurations:/configurations:ro + - ./traefik/letsencrypt/acme.json:/acme.json + labels: + - "traefik.enable=true" + - "traefik.docker.network=frontend" + - "traefik.http.routers.traefik-secure.entrypoints=https" + - "traefik.http.routers.traefik-secure.rule=Host(`dashtraefik.yourdomain.com`)" + - "traefik.http.routers.traefik-secure.service=api@internal" + - "traefik.http.routers.traefik-secure.middlewares=user-auth@file" + +networks: + frontend: + external: true +``` + +Si ya tenemos nuestro DNS configurado para que el nombre +`dashtraefik.yourdomain.com` se resuelva correctamente ya podemos +arrancar nuestro _Traefik_: + +```bash +cd ~/work/docker/ejemplo_03 +docker-compose --verbose up -d +``` + +Ya tenemos nuestro contenedor _Traefik_ funcionando correctamente. +Estamos listos para añadir nuevos servicios a nuestra plataforma. + +### Nuestro primer servicio: Portainer + +Vamos a definir nuestro primer servicio conectado por _Traefik_ en el +mismo fichero `docker-compose.yml`. Añadimos la definición del nuevo +servicio en las lineas que van de la 29 a la 47: + +```yaml +version: '3' + +services: + traefik: + image: traefik:v2.4 + container_name: traefik + restart: unless-stopped + security_opt: + - no-new-privileges:true + networks: + - frontend + ports: + - 80:80 + - 443:443 + volumes: + - /etc/localtime:/etc/localtime:ro + - /var/run/docker.sock:/var/run/docker.sock:ro + - ./traefik/traefik.yml:/traefik.yml:ro + - ./traefik/configurations:/configurations:ro + - ./traefik/letsencrypt/acme.json:/acme.json + labels: + - "traefik.enable=true" + - "traefik.docker.network=frontend" + - "traefik.http.routers.traefik-secure.entrypoints=https" + - "traefik.http.routers.traefik-secure.rule=Host(`dashtraefik.yourdomain.com`)" + - "traefik.http.routers.traefik-secure.service=api@internal" + - "traefik.http.routers.traefik-secure.middlewares=user-auth@file" + + portainer: + image: portainer/portainer-ce:2.0.1-alpine + container_name: portainer + restart: unless-stopped + security_opt: + - no-new-privileges:true + networks: + - frontend + volumes: + - /etc/localtime:/etc/localtime:ro + - /var/run/docker.sock:/var/run/docker.sock:ro + - ./portainer:/data + labels: + - "traefik.enable=true" + - "traefik.docker.network=frontend" + - "traefik.http.routers.portainer-secure.entrypoints=https" + - "traefik.http.routers.portainer-secure.rule=Host(`portainer.yourdomain.com`)" + - "traefik.http.routers.portainer-secure.service=portainer" + - "traefik.http.services.portainer.loadbalancer.server.port=9000" + +networks: + frontend: + external: true +``` + +Añadimos un nuevo servicio `portainer`, con las opciones de rearranque +y de seguridad iguales a las de _Traefik_. Mapeamos también el fichero +`/etc/localtime` para que el contenedor se ponga en hora con el +_host_; y el _socket_ `docker.sock` que _Portainer_ necesita +(_Portainer_ es un _frontend_ para _Docker_) + +Añadimos también las etiquetas para informar a _Traefik_ del nuevo +servicio. +* El servicio se llama _portainer_ +* Acepta peticiones en el puerto 9000 +* _Portainer_ usará el _entry point_ `https` de _Traefik_ +* Y la regla de enrutado será ``Host(`portainer.yourdomain.com`)`` + + + +Una vez completada la configuración del DNS, podemos levantar nuestro +nuevo servicio con: + +```bash +docker-compose up -d portainer +``` + +{{< admonition type=warning title="Login en Portainer" state=open >}} + +_Portainer_ implementa su propio sistema de gestión y autenticación de +usuarios. Además da problemas si activamos el _middleware_ `user-auth` + +**Tendrás que crear un usuario `admin` y protegerlo con contraseña tan +pronto como lo arranques.** + +{{< /admonition >}} diff --git a/content/posts/notes_general/notes_videoprod.md b/content/posts/notes_general/notes_videoprod.md new file mode 100644 index 0000000..f7ed41b --- /dev/null +++ b/content/posts/notes_general/notes_videoprod.md @@ -0,0 +1,26 @@ +--- +weight: 4 +title: "Apuntes de Video" +date: 2021-11-05T21:56:02+0100 +draft: true +summary: "Apuntes incompletos de Video" +categories: +- notes +tags: +- obs +- owncast +- streaming +- videocast +--- + +## OBS + +## kdenlive + +## shotcut + + +## Referencias + +- [Consejos para livestream de video](https://steele.blue/livestream-setup/) +- [Curso de kdenlive de podcastlinux](https://devtube.dev-wiki.de/video-channels/canalpodcastlinux/videos) diff --git a/content/posts/notes_general/notes_wireguard.md b/content/posts/notes_general/notes_wireguard.md new file mode 100644 index 0000000..a6eb3fa --- /dev/null +++ b/content/posts/notes_general/notes_wireguard.md @@ -0,0 +1,502 @@ +--- +weight: 4 +title: "Apuntes de WireGuard" +date: 2021-04-18T22:20:51+0200 +draft: false +summary: "Apuntes de WireGuard" +categories: + - notes +tags: + - wireguard + - debian + - alpine +--- + +Apuntes sobre WireGuard. Una herramienta para implementar redes VPN + + + +{{< admonition type=warning title="Work in progress" open=true >}} +Estos apuntes no están completos, (ni de lejos) +{{< /admonition >}} + + +# WireGuard + +{{< admonition type=abstract title="Referencias" open=false >}} + + +- [WireGuard: Conceptual Overview](https://www.wireguard.com/#conceptual-overview) +- [WireGuard on Ubuntu](https://www.xmodulo.com/wireguard-vpn-server-ubuntu.html) +- [Set up WireGuard on Alpine Linux](https://www.cyberciti.biz/faq/how-to-set-up-wireguard-vpn-server-on-alpine-linux/) +- [Firewall rules for WireGuard](https://www.cyberciti.biz/faq/how-to-set-up-wireguard-firewall-rules-in-linux/) +- [Debian 10 set up WireGuard VPN server](https://www.cyberciti.biz/faq/debian-10-set-up-wireguard-vpn-server/) +- [Setting up a private mesh VPN with WireGuard](https://www.scaleway.com/en/docs/wireguard-mesh-network/) +- [Wireguard network managers](https://kaspars.net/blog/wireguard-managers) + - [wg-gen-web](https://github.com/vx3r/wg-gen-web) + +{{< /admonition >}} + + + + +Las implementaciones tradicionales de VPN (_**V**irtual **P**rivate **N**etwork_) más utilizadas en la industria (como _IPsec_ por ejemplo) se implementan dentro del kernel; insertando una capa extra, que se encarga del cifrado y el encapsulado, entre la pila IP y el interfaz de red. + +Esto se hace así por que se quiere que sea un proceso tan rápido como sea posible que no penalice la eficiencia de las comunicaciones. A cambio es necesario implementar por separado el _plano de tráfico_ (la capa que hemos mencionado dentro del kernel) y _el plano de control_ que se crea en el espacio de usuario. Esta arquitectura hace que tanto la implementación (el software) como la gestión de estas soluciones sean complejas. + +En las implementaciones de VPN en el espacio de usuario, como por ejemplo _OpenVPN_, todo el proceso de cifrado lo hace un demonio en el espacio de usuario. Con esta arquitectura inevitablemente se penaliza el rendimiento, por qué los paquetes tienen que pasar del espacio kernel al espacio de usuario y viceversa varias veces. Aunque esto no impide que sean soluciones muy populares debido a su facilidad de uso. + +_WireGuard_ es una alternativa relativamente reciente a _OpenVPN_ que puede ser muy interesante. Por un lado se implementa como un módulo del Kernel, y por otro mantiene la gestión y configuración a un nivel muy asequible por qué usa ficheros de configuración **muy sencillos** y se basa en la metáfora de _interfaz de red_, que se gestiona como cualquier otro interfaz del sistema con las herramientas estándar como `ip` o `ifconfig`. + +_WireGuard_ cifra y encapsula paquetes IP sobre UDP. Básicamente hay que añadir un interface _WireGuard_, configurarlo con tu clave privada y la clave pública de los pares (_peers_) con los que se va a comunicar y ya puedes enviar paquetes a través de ese túnel. En realidad es mucho más parecido a _SSH_ o _Mosh_ que a _OpenVPN_. + +Como hemos dicho, al usar _WireGuard_ se añadirán uno (o varios) interfaces de red de tipo _WireGuard_ (wg0, wg1, ....) a tu máquina. Estos interfaces de red se pueden configurar normalmente con las herramientas del sistema, igual que un `eth0` o `wlan0`. Los aspectos específicos del _WireGuard Interface_ se configuran con la herramienta `wg`. Los interfaces _WireGuard_ se comportan como un túnel cifrado. + +_WireGuard_ asocia las direcciones IP de los túneles con los extremos remotos y sus claves públicas. + +Cuando el interfaz envía un paquete a un _peer_ hace lo siguiente: + +1. Veamos, este paquete es para `192.168.30.8`. ¿Qué _peer_ es este? Vale, es para el _peer_ `pepito`. Si no soy capaz de determinar a que _peer_ esta dirigido descartaré el paquete (_drop_) **¡OJO!** WireGuard determina el _peer_ a partir de la IP destino +2. Cifra el paquete con la clave pública del _peer_ `pepito` +3. ¿Qué extremo remoto corresponde a este _peer_? Vale, es el puerto UDP 53133 en 216.58.211.110 +4. Procedo a enviar los bytes cifrados al extremo remoto via UDP + +En el extremo remoto, el _peer_ `pepito`que recibe el paquete: + +1. Me ha llegado un paquete del puerto `7361` de `98.139.183.24`. Voy a descifrarlo. +2. Descifro y además compruebo que se garantiza la autenticidad para el peer `manolito`. Ya que estamos actualizo mi tabla de direcciones: Apunto que el destino más reciente para `manolito` es el `98.139.183.24:7361/UDP` +3. El paquete descifrado viene de `192.168.43.89` si el _peer_ `manolito` está autorizado a enviar paquetes con esa IP origen lo aceptamos, de lo contrario lo descartamos (_drop_) + +El concepto fundamental en el que se base _WireGuard_ es el ___Cryptokey Routing___, consiste en almacenar una lista de claves públicas junto con la lista de direcciones IP origen que permitimos, ambas asociadas al _peer_ al que corresponde esa clave. + +Así: +- Cada interfaz de red tiene una clave privada +- Cada interfaz de red tiene una lista de _peers_ +- Cada _peer_ tiene una clave pública +- Para cada _peer_ se especifican las direcciones IP origen autorizadas + +Un _servidor_ de _WireGuard_ podría tener una configuración como esta (no está completa es una configuración simplificada): + +```toml +[Interface] +PrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk= +ListenPort = 51820 + +[Peer] +PublicKey = xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg= +AllowedIPs = 10.192.122.3/32, 10.192.124.1/24 + +[Peer] +PublicKey = TrMvSoP4jYQlY6RIzBgbssQqY3vxI2Pi+y71lOWWXX0= +AllowedIPs = 10.192.122.4/32, 192.168.0.0/16 + +[Peer] +PublicKey = gN65BkIKy1eCE9pP1wdc8ROUtkHLF2PfAqYdyYBz6EA= +AllowedIPs = 10.10.10.230/32 +``` + +Un _cliente_ puede tener una configuración más simple (no está completa): + +```toml +[Interface] +PrivateKey = gI6EdUSYvn8ugXOt8QQD6Yc+JyiZxIhp3GInSWRfWGE= +ListenPort = 21841 + +[Peer] +PublicKey = HIgo9xNzJMWLKASShiTqIybxZ0U3wGLiUeJ1PKf8ykw= +Endpoint = 192.95.5.69:51820 +AllowedIPs = 0.0.0.0/0 +``` + +Tanto el _"servidor"_ como el _"cliente"_ tienen un interfaz de red (al menos) con su clave privada asociada. Pero no te lies con los términos servidor y cliente, nada impide que configures varios interfaces de tu VPN como servidores. + +El _servidor_ tiene también una lista de _peer_; cada uno con su lista `AllowedIPs`: la lista de direcciones IP origen que están autorizadas para ese _peer_ + +La configuración mínima para un cliente solo necesita tener como _peer_ al servidor. Se almacena una dirección ___inicial___ del servidor. + +Deliberadamente no se mantienen direcciones estáticas para los _peer_, los _peer_ se identifican al descifrar un paquete recibido. Y se mantiene una tabla de direcciones más recientes para cada _peer_ que se actualiza con cada mensaje recibido. De esta forma los _peer_ podrían hacer _roaming_ por distintas redes. + +Cuando el servidor recibe un paquete comprueba, una vez descifrado, que el _peer_ puede enviar paquetes con ese origen, en caso que no sea así lo descarta. + +Cuando el servidor quiere enviar un paquete comprueba las redes asociadas a cada _peer_ para decidir a quien enviarlo, y lo envía a la dirección IP más reciente que tenga registrada para ese _peer_. + +En el cliente (con la configuración de ejemplo) se aceptan paquetes de cualquier origen si provienen del _servidor_. También se envian todos los paquetes, sea cual sea la dirección de destino hacia el _servidor_. Como hemos dicho, los paquetes se envían a la dirección registrada más recientemente para el servidor, si el cliente ya ha recibido algún paquete, o a la dirección inicial especificada en la configuración si aún no ha recibido nada desde el _servidor_. + +## Instalación de _WireGuard_ + + +### Instalación de WireGuard en Debian Server 10 + +Añadimos `backports` a nuestros orígenes de software e instalamos _WireGuard_. + +```bash +echo 'deb http://ftp.debian.org/debian buster-backports main' | sudo tee /etc/apt/sources.list.d/buster-backports.list +apt update +apt install wireguard +``` + +_WireGuard_ se ejecuta como un **módulo del kernel** + +Genemos las claves en `/etc/wireguard` + +```bash +umask 077 +wg genkey | sudo tee /etc/wireguard/privatekey | wg pubkey | sudo tee /etc/wireguard/publickey +``` + +Editamos el fichero `sudo nano /etc/wireguard/wg0.conf` con el contenido: + +```toml +[Interface] +Address = 10.0.0.1/24 +SaveConfig = true +ListenPort = 51820 +PrivateKey = SERVER_PRIVATE_KEY +PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o ens3 -j MASQUERADE +PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o ens3 -j MASQUERADE +``` + +El fichero de configuración no tiene por que ser accesible para nadie (`chmod 600 /etc/wireguard/wg0.conf`), al fin y al cabo contiene la clave privada. + +Este fichero de configuración correspondería a un "servidor" de VPN clásico para dar acceso a internet a los "clientes" de la VPN via NAT. + +Los campos del fichero de configuración: + + +**Address** + +: La dirección IP **privada** (una dirección interna de la VPN) asignada al interfaz. Asignamos IP y máscara en un solo comando. + +**SaveConfig** + +: Si vale `true`la configuración del interfaz se salva en el fichero de configuración cada vez que hacemos un shutdown del interfaz + +**ListenPort** + +: El puerto UDP que escucha nuestro interfaz, los _peer_ (clientes) tienen que dirigirse a ese puerto + +**PrivateKey** + +: La clave de cifrado para este interfaz + +**PostUp** + +: Un comando o un script que se tiene que ejecutar **antes** de levantar el interfaz. En este ejemplo estamos habilitando _IP masquerading_. Esto permitirá que el tráfico salga del servidor dando a los clientes de la VPN acceso a internet (habría que sustituir `ens3` por el verdadero nombre del interfaz público del servidor) + +**PostDown** + +: Un comando o script que se tiene que ejecutar **antes** de bajar el interface. En nuestro ejemplo se borran la configuración de _IP masquerading_ de las IPtables. Es imprescindible sustituir `ens3` por el puerto real del servidor. + + +Podemos arrancar ya el interfaz `wg0` con el comando `wg-quick up wg0` (en mi caso necesité reiniciar el servidor previamente, supongo que para completar la instalación del módulo wireguard en el kernel) + +```bash +root@vmi504132:~# wg-quick up wg0 +[#] ip link add wg0 type wireguard +[#] wg setconf wg0 /dev/fd/63 +[#] ip -4 address add 10.0.0.1/24 dev wg0 +[#] ip link set mtu 1420 up dev wg0 +[#] iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE +root@vmi504132:~# wg show wg0 +interface: wg0 + public key: rgbUE5KCfrUWT/7Vhh7NTdosCyP9LGx7M5vnJU9EAxw= + private key: (hidden) + listening port: 51820 +root@vmi504132:~# ip addr show wg0 +24: wg0: mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000 + link/none + inet 10.0.0.1/24 scope global wg0 + valid_lft forever preferred_lft forever +``` + +Hasta aquí hemos configurado nuestro nuevo interfaz `wg0` pero necesitamos completar un par de cosas más antes de terminar: + +#### Abrir el cortafuegos para WireGuard + +Tenemos que permitir tráfico entrante UDP en el puerto `51820`, en mi caso estoy usando UFW en el servidor: + +```bash +ufw allow 51820/udp +ufw enable +ufw status numbered +ufw status verbose +``` + +#### Habilitar _ip forwarding_ + +Editamos el fichero `/etc/sysctl.conf` y descomentamos la linea: + +`net.ipv4.ip_forward=1` + +Recargamos el fichero ejecutando `sysctl -p /etc/sysctl.conf` + +Evidentemente si queremos usar IPV6 tenemos que descomentar la linea correspondiente. + +#### Configurar el arranque automático del interfaz cuando arranca el servidor + +Para dejar configurado el arranque automático del interfaz `wg0`: + +```bash +wg-quick down wg0 # Paramos el interfaz que hemos levantado manualmente + +systemctl enable wg-quick@wg0 # Dejamos el servicio habilitado +systemctl status wg-quick@wg0 # Comprobamos el status del servicio + + +# systemctl start wg-quick@wg0 # Comandos para arrancar o parar el servicio +# systemctl stop wg-quick@wg0 # manualmente +``` + + +### Creación de una máquina virtual en Proxmox con Alpine Linux y WireGuard + +Creamos la máquina virtual igual que creamos la de Docker + +1. Creamos la máquina virtual, basada en la ISO de _Alpine Linux_, asignamos un disco duro de 16Gb +2. Ejecutamos `alpine-setup` asignamos IP estática y el tipo de instalación `sys` +2. Instalamos `qemu-guest-agent` y configuramos adecuadamente +3. Instalamos `sudo` y habilitamos el grupo `wheel` (editando `/etc/sudoers`) +3. Añadimos usuario `wgadmin` (ya de paso lo metemos en `wheel`) +4. Instalamos UFW +5. Instalamos el WireGuard + + ```bash + apk add sudo + addgroup wgadmin + adduser -G wgadmin wgadmin + adduser wgadmin wheel + apk add ufw + ufw default deny incoming + ufw default allow outgoing + ufw allow ssh + ufw enable + apk add wireguard-tools + ``` + +Ya tenemos todo listo para proceder con la configuración de _WireGuard_ + +```bash +cd /etc/wireguard +umask 077; wg genkey | tee privatekey | wg pubkey > publickey +``` + +Creamos el fichero de configuración `wg0.conf` + +```toml +[Interface] +Address = 10.0.0.2/24 +ListenPort = 51820 +PrivateKey = 2IHAoIOz+tWxRZ4oRlI1xHiD5FADVBTKOePnLSZNSkw= + + +[Peer] +PublicKey = d7TRyBXIX0I3sC/tPOqrIpNAGwEt2bW43QXKK4cEeGw= +AllowedIPs = 0.0.0.0/0 +Endpoint = 207.180.207.206:51820 +PersistentKeepalive = 20 +``` + +Habilitamos el _IP Forwarding_: + +Por comando: `sysctl net.ipv4.ip_forward=1` + +O si lo queremos permanente añadimos la linea `net.ipv4.ip_forward=1` en el fichero `/etc/sysctl.conf` + +También tenemos que abrir el cortafuegos `ufw allow 51820/udp` + +Ya podemos levantar el interfaz con `wg-quick up wg0` + +Para que la cosa funcione tenemos que habilitar la redirección del puerto 51820/udp en nuestro router de internet para que apunte a la máquina virtual que hemos creado. + +Llegamos por ping a la maquina siriowg pero no más allá (faltan rutas de vuelta hacia el servidor) + +