Crear y publicar sitios web estáticos con Emacs y Org-Mode

Introducción

Hola, este es mi primer post en mi blog personal, y la principal motivación del mismo es simplemente la de servir de impulso para comenzar a dar algo de contenido a este sitio web que he creado para depositar (algunos de) mis pensamientos y avances en (algunos de) mis campos de interés, como la informática, el software libre y la electrónica, principalmente. Y como no se me ha ocurrido nada mejor ni más original, aquí simplemente expondré el proceso de creación del sitio. Los archivos fuente del contenido del sitio han sido escritos en el programa Emacs, empleando el al mismo tiempo "modo" de escritura y lenguaje de marcado ligero Org-Mode / org. Para generar los archivos en formato html que constituyen el contenido final que estás viendo, he utilizado la función integrada de publicación org-publish, y para hospedarlo, el servicio de hospedaje de sitios web estáticos de Source Hut, plataforma destinada porincipalmente al almacenamiento de repositorios de código similar a las más populares GitHub o GitLab. En futuros posts probablemente exponga y profundice un poquito más en estas cosas que acabo de mencionar.

Dado que todavía no tengo mucha experiencia en el asunto, y que tampoco me quería complicar demasiado la vida, he seguido un tutorial para completar el proceso de crear y publicar esta página disponible en https://systemcrafters.net/publishing-websites-with-org-mode/building-the-site/, página que, como las demás partes del proyecto System Crafters, recomiendo a quien tenga interés en los mismos temas que trato en este blog. Eso sí, he adaptado un poco el proceso a mi propia forma de hacer las cosas. Todo el proceso ha sido realizado en una máquina con sistema operativo Linux Manjaro.

Construcción del script y generación del sitio

Para pasar del formato org en que está escrito este sitio al formato html típico de páginas web1, podemos elaborar un script que permita realizar el proceso de forma automática. Esto lo conseguimos mediante el sistema ox-publish y su función org-publish, generando todos los archivos .html a partir de los archivos fuente .org, traduciendo entre formatos la estructura de los documentos, enlaces, referencias, etcétera. Además, mediante Rsync podremos copiar todos los archivos fuente y recursos del directorio de origen con los del directorio de destino, que albergará todo el contenido final del sitio2.

construir-sitio.el

La llamada a la función de exportación con toda la configuración de la misma la pondremos en el archivo construir-sitio.el.

  1. Primero, necesitaremos cargar el sitema de publicación ox-publish mencionado:

    ;; Cargar el sistema de publicación
    (require 'ox-publish)
    
  2. Para publicar nuestro sitio, añadiremos el siguiente fragmento al archivo, con un puñado de opciones de personalización, si queremos:

    ;; Mensaje de confirmación al inicio
    (message "Build iniciado.")
    
    ;; Definimos el proyecto a publicar
    (setq org-publish-project-alist
          (list
             (list "bitib"
                   :recursive t
                   :base-directory "./content"
                   :publishing-directory "./public"
                   :publishing-function 'org-html-publish-to-html
                   :with-author nil ; No incluir el nombre del autor
                   :with-creator t ; Incluir las versiones de Emacs y Orgmode
                   :with-toc t ; Incluir un índice de contenido
                   :section-numbers nil ; No numerar las secciones
                   :time-stamp-file nil ; No incluir marca de fecha y hora de creación
                   :html-validation-link nil ; No incluir enlace de validación
                   :language "es" ; Idioma
                   ;; Plantilla para el renderizado de expresiones matemáticas como texto
                   :html-mathjax-template
                   "<link rel='stylesheet' href='/katex/katex.css'/>
                    <script defer='defer' src='/katex/katex.js'></script>
                    <script defer='defer' src='/katex/contrib/auto-render.js'
                            onload='renderMathInElement(document.body);'></script>"
                   )))
    
      ;; Generamos los archivos de salida (content -> public)
      (org-publish-all t)
    
      ;; Mensaje de confirmación al finalizar
      (message "¡Build completo!")
    )
    

    La primera lista es la de todas las configuraciones de proyectos (por si hubiera más de uno), mientras que la segunda, anidada, corresponde a este primer proyecto. Cada vez que llamemos al script, se generará todo el contenido del sitio web. Al ser un pequeño sitio web estático, no supone un gran gasto de tiempo y recursos, y nos ahorra complicaciones. Las posibles opciones de exportación son muchas. Para una lista completa de estas opciones podemos acudir al manual de orgmode.

  3. En mi caso, he añadido algunas otras configuraciones para la personalización de la apariencia de mi sitio:

    ;; Opciones de configuración preliminares
    ;; Eliminar scripts predeterminados
    (setq org-html-head-include-scripts nil)
    ;; Eliminar estilo predeterminado
    (setq  org-html-head-include-default-style nil)
    ;; Estilo personalizado
    (setq  org-html-head
           "<link rel='stylesheet' href='/css/estilo.css'/>")
    ;; Cabecera personalizada
    (setq org-html-preamble
          "<nav><a href='/index.html'>CASA</a><a href='/posts.html'>POSTS</a></nav>")
    ;; Método de resaltado de sintaxis para los bloques de código
    (setq  org-html-htmlize-output-type 'css)
    
  4. Podemos hacer uso de paquetes adicionales para modificar o ampliar ciertos aspectos. Por ejemplo, para conseguir resaltado de sintaxis disponemos del paquete htmlize:

    ;; Configurar directorio de instalación dentro de nuestro proyecto
    (require 'package)
    ;; Incluir repositorios de paquetes utilizados
    (setq package-user-dir (expand-file-name "./.packages"))
    (setq package-archives '(("melpa" . "https://melpa.org/packages/")
                             ("elpa" . "https://elpa.gnu.org/packages/")))
    
    ;; Inicializar el sistema de gestión de paquetes
    (package-initialize)
    (unless package-archive-contents
      (package-refresh-contents))
    
    ;; Instalar dependencias
    (unless (package-installed-p 'htmlize)
      (package-install 'htmlize))
    

    Indico un dirrectorio de instalación dentro del directorio del proyectopara que los paquetes utilizados para el sitio no se mezclen con los de mi configuración local personal.

  5. Finalmente, el archivo quedaría como sigue:

    ;; Configurar directorio de instalación dentro de nuestro proyecto
    (require 'package)
    (setq package-user-dir (expand-file-name "./.packages"))
    (setq package-archives '(("melpa" . "https://melpa.org/packages/")
                               ("elpa" . "https://elpa.gnu.org/packages/")))
    
    ;; Inicializar el sistema de gestión de paquetes
    (package-initialize)
    (unless package-archive-contents
      (package-refresh-contents))
    
    ;; Instalar dependencias
    (unless (package-installed-p 'htmlize)
      (package-install 'htmlize))
    
    ;; Cargar el sistema de publicación
    (require 'ox-publish)
    
    ;; Opciones de configuración preliminares
    ;; Eliminar scripts predeterminados
    (setq org-html-head-include-scripts nil)
    ;; Eliminar estilo predeterminado
    (setq  org-html-head-include-default-style nil)
    ;; Estilo personalizado
    (setq  org-html-head
           "<link rel='stylesheet' href='/css/estilo.css'/>")
    ;; Cabecera personalizada
    (setq org-html-preamble
          "<nav><a href='/index.html'>CASA</a><a href='/posts.html'>POSTS</a></nav>")
    ;; Método de resaltado de sintaxis para los bloques de código
    (setq  org-html-htmlize-output-type 'css)
    
    ;; Mensaje de confirmación al inicio
    (message "Build iniciado.")
    
    ;; Definimos el proyecto a publicar
    (setq org-publish-project-alist
          (list
             (list "bitib"
                   :recursive t
                   :base-directory "./content"
                   :publishing-directory "./public"
                   :publishing-function 'org-html-publish-to-html
                   :with-author nil ; No incluir el nombre del autor
                   :with-creator t ; Incluir las versiones de Emacs y Orgmode
                   :with-toc t ; Incluir un índice de contenido
                   :section-numbers nil ; No numerar las secciones
                   :time-stamp-file nil ; No incluir marca de fecha y hora de creación
                   :html-validation-link nil ; No incluir enlace de validación
                   :language "es" ; Idioma
                   ; Plantilla para el renderizado de expresiones matemáticas como texto
                   :html-mathjax-template
                   "<link rel='stylesheet' href='/katex/katex.css'/>
                    <script defer='defer' src='/katex/katex.js'></script>
                    <script defer='defer' src='/katex/contrib/auto-render.js'
                            onload='renderMathInElement(document.body);'></script>"
                   )))
    
      ;; Generamos los archivos de salida (content -> public)
      (org-publish-all t)
    
      ;; Mensaje de confirmación al finalizar
      (message "¡Build completo!")
    

construir-sitio.sh

Para ejecutar el código del archivo que acabamos de crear, podemos abrirlo en Emacs y evaluarlo, pero también podemos incluir una llamada en un ejecutable bash, de modo que cada vez que se ejecute el último, también lo hará el primero.

  1. Creamos el script y llamamos a Emacs para que evalúe y ejecute el código de construir-sitio.el:

    #!/bin/sh
    emacs -Q --script construir-sitio.el
    

    El parámetro -Q facilita que el script pueda funcionar sin problemas en distintos sistemas, ya que le indica al programa que no emplee la configuración del usuario actual, evitando posibles interferencias.

  2. Llamamos también a Rsync para que realice la copia entre directorios de origen y destino, bautizados en este caso como content y public, respectivamente:

    rsync -av ./content/* ./public
    

    Donde av responde a --archive y verbose.

  3. Nuestro script queda así:

    #!/bin/sh
    emacs -Q --script construir-sitio.el
    rsync -av --delete-after --exclude={'*.org~','*.html','*.xml','.git','.build.yml','LICENSE.txt'} ./content/ ./public
    

    Excluímos archivos y directorios que no nos interese copiar al directorio de destino3, o que no nos interese borrar en este último 4.

  4. Debemos añadir la ruta de nuestro script al path para su ejecución:

    export PATH="/ruta/a/nuestro/script:$PATH"  
    
  5. Finalmente, necesitaremos hacerlo ejecutable:

    chmod u+x ../../../construir-sitio.sh
    

    Deberemos adaptar la ruta a la ubicación y nombre del ejecutable en cada caso, por supuesto. Es conveniete probar el funcionamiento de forma que nos asefuremos de que hace los que queremos y no nos borra o se "olvida" de nada importante.

Previsualización del sitio

Para previsualizar el aspecto que tendrá nuestro sitio web antes de subirlo como tal, disponemos de un paquete de Emacs denominado simple-httpd, que genera un servidor local desde el que podremos acceder a través de cualquier navegador en nuestro equipo:

(unless (package-installed-p 'simple-httpd)
  (package-install 'simple-httpd))

Al ejecutar httpd-serve-directory se nos preguntará por un directorio para el servidor. Le indicaremos el directorio public y abriremos en un navegador la dirección "http://localhost:8080" para acceder a la previsualización.

Publicación del sitio

Para hacer públicamente accesible nuestro sitio una vez generado en local, debemos realizar una serie de pasos adicionales. Para ello haremos uso de git, una herramienta de control de versiones que como tal nos permitirá monitorizar y gestionar los cambios realizados en el contenido del sitio a lo largo del tiempo, y que será necesaria para subir el contenido a un repositorio de plataformas como Source Hut, que es la que yo he elegido, y donde se hospedará nuestro sitio web estático.

  1. Crear un repositorio git:

    git init
    
  2. Añadir todo el contenido del directorio donde se encuentra el contenido de nuestro sitio:

    git add -A
    
  3. Hacer una captura del dicho contenido (commit):

    git commit -m "Primer commit"
    
  4. Si es la primera vez que usamos git, es probable que se queje de que no hemos agregado un nombre de usuario y una dirección de correo. Entonces deberemos añadir estos datos:

    git config --global user.email "<nombre>@<extension>"
    git config --global user.name "<nombre>"
    
  5. Si queremos aprovechar alguno de los servicios de creación de sitios estáticos como los ofrecidos por GitHub, GitLab o Source Hut, debemos crear una cuenta en uno (o varios) de estos sitios. Los dos primeros permiten su uso de forma gratuita, mientras que el último requiere de una subscripción de 2$ al mes ([https://man.sr.ht/ops/builds.sr.ht-migration.md][Gracias cryptobros]). Aquí se explica el proceso para el último, dado que ha sido el escogido por mi persona, pero es similar para los otros (y hay muchísimos tutoriales a lo largo y ancho de Internet).
  6. Una vez creada la cuenta en Source Hut, debemos crear una clave SSH para subir el contenido de forma segura:

    ssh-keygen -t ed25519 -C "<nombre>@<extension>"
    

    Se nos preguntará por una ruta para ubicar la clave, y una frase de seguridad.

  7. A continuación, debemos crear un repositorio online donde colocar el contenido de la página.
  8. Una vez creado el repositorio, podemos añadirlo como repositorio remoto a nuestro repositorio local:

    git remote add origin git@git.sr.ht:~<nombre-de-usuario>/<nombre-de-repositorio>
    

    Si ya tuviéramos un repositorio remoto "origin", debemos poner otro nombre. Por ejemplo:

    git remote add srht git@git.sr.ht:~<nombre-de-usuario>/<nombre-de-repositorio>
    
  9. Finalmente, subimos el contenido del repositorio local al repositorio público:

    git push origin master
    

    o:

    git push srht master
    

    El repositorio debe contener una de las licencias disponibles, cuyo texto debemos copiar en un archivo con el título "LICENSE" o "COPYING". En mi caso he escogido la GPLv3, de modo que cualquier persona está autorizada a compartir y copiar su contenido.

  10. Para generar el sitio una vez subido el contenido al repositorio remoto, podemos crear un archivo yaml para que Source Hut lo haga automáticamente. Por ejemplo:

    image: alpine/edge
    oauth: pages.sr.ht/PAGES:RW
    sources:
      - https://git.sr.ht/~bitib/bitib
    packages:
    - hut
    environment:
      site: bitib.srht.site
    tasks:
    - package: |
        cd ./bitib
        tar -cvz . > ../site.tar.gz
    - upload: |
        hut pages publish -d $site site.tar.gz
    

    Debemos cambiar fundamentalmente tres cosas:

    1. La dirección de origen en sources.
    2. La dirección del sitio a generar en environment.
    3. La ruta del directorio donde se hará la generación (build). Aquí es ./bitib.

    Para asegurarnos de que el proceso se lleva a cabo correctamente, acudimos a https://builds.sr.ht/. Con esto estaríamos.

Conclusión

Cada vez que hagamos un cambio local en el contenido de nuestro sitio, invocaremos nuevamente al script de construcción, y cuando queramos subirlo, debemos hacer un nuevo commit y push desde el repositorio local al remoto. Entonces la (re)generación del sitio será automática. Como se ha mencionado, si durante la realización de cambios queremos previsualizar el resulato, tan solo hace falta ejecutar la función httpd-serve-directory la primera vez durante la sesión, acudir a http://localhost:8080/, y refrescar la página en caso de posteriores cambios.

El proceso seguido puede resultar algo laborioso la primera vez, pero a partir de entonces, y gracias a nuestro script, será mucho más rápido y sencillo. Para agilizar la subida del sitio podemos configurar la autentificación con SSH entre otras cosas, pero eso lo dejo para más adelante.

Notas al pie de página:

1

Bajo el protocolo http.

2

También podríamos conseguir esto mediante la propia función org-publish, pero considero que de esta otra forma resulta más cómodo y sencillo

3

En este caso los archivos de recuperación (.org~) generados por Emacs

4

Entre estos se encuentran el directorio con el repositorio de git y los archivos de construcción y licencia del sitio, que solo son relevantes para el repositorio remoto, tal y como se muestra en la sección Publicación del sitio

RSS