Hugo en GitHub

Cómo crear un blog de Hugo y publicarlo en GitHub pages

Llevaba tiempo queriendo migrar mi blog de google a otra plataforma y Hugo y GitHub me lo han puesto muy fácil.

Si a esto le sumamos Typora para escribir el post, parece factible que escribir un post no sea un dolor.

Hugo

Instalar Hugo es tan sencillo como descargar un fichero ejecutable. Si la plantilla que elijas más adelante te especifica que usa la versión extended de Hugo, simplemente baja esa versión desde https://github.com/gohugoio/hugo/releases.

Donde he invertido más tiempo ha sido en buscar y encontrar una plantilla. En mi caso he optado por Beautiful Hugo.

Con todo listo, crear un sitio de Hugo conlleva una sola instrucción:

hugo new site panicoenlaxbox-blog

Para trabajar con la plantilla, puedes o bien copiar los ficheros a tu repositorio, o bien añadir un submódulo a tu repositorio que apunte al repositorio de la plantilla. Con la segunda opción, siempre tendrás la puerta abierta a actualizar el submódulo e incorporar nuevos cambios del repositorio remoto de una forma más sencilla.

Si optas por por copiar los ficheros, un script como el siguiente debería bastar (o simplemente descargar manualmente el repositorio y copiarlo a mano).

$url = "https://github.com/halogenica/beautifulhugo/archive/refs/heads/master.zip"
Invoke-WebRequest -Uri $url -OutFile .\themes\beautifulhugo.zip
Expand-Archive .\themes\beautifulhugo.zip -DestinationPath .\themes\
Rename-Item -Path .\themes\beautifulhugo-master\ -NewName beautifulhugo
Remove-Item .\themes\beautifulhugo.zip
Copy-Item .\themes\beautifulhugo\exampleSite\config.toml .\config.toml

Dicho esto, yo he optado por crear un submódulo, así que después de haber creado el sitio, he tenido que ejecutar:

git init
git submodule add https://github.com/halogenica/beautifulhugo.git .\themes\beautifulhugo

Si más adelante tienes que volver a clonar tu repositorio, usa la siguiente instrucción para clonar también el submódulo:

git clone --recurse-submodules <your_repository>

Para una primera prueba en local y confirmar que está todo funcionando:

Copy-Item .\themes\beautifulhugo\exampleSite\config.toml .\config.toml
hugo new post/my-first-post.md
hugo new page/about.md
hugo server -D

Un paso obligado es modificar el fichero config.toml que hemos copiado de la plantilla, los cambios más relevantes son:

baseurl = "https://panicoenlaxbox.github.io"
DefaultContentLanguage = "es"
title = "Programación desordenada"
disqusShortname = ...
googleAnalytics = ...
[Params]
  subtitle = "Lo que se me vaya ocurriendo"
  readingTime = false
  wordCount = false
  hideAuthor = true
[Author]
  ...
# [[menu.main]]
#     identifier = "samples"
#     ...
# 
# [[menu.main]]
#     parent = "samples"
#     ...
# 
# [[menu.main]]
#     parent = "samples"
#     name = "Math Sample"
#     ...
# 
# [[menu.main]]
#     parent = "samples"
#     name = "Code Sample"
#     ...

Como verás, la plantilla seleccionada viene de serie con soporte para Disqus y Google Analytics.

Para modificar la plantilla, podemos hacer una copia en nuestro proyecto de los ficheros que queremos sobrescribir, y Hugo lo tendrá en cuenta para hacer que prevalezcan sobre los que se encuentre en la plantilla. Por ejemplo, yo he querido editar, entre otros, el fichero share-links.html.

New-Item .\layouts\partials -ItemType Directory
Copy-Item .\themes\beautifulhugo\layouts\partials\share-links.html .\layouts\partials\share-links.html
# Ahora puedes modificar tu fichero share-links.html y prevalecerá sobre el que está en la plantilla

Para la imagen principal del blog, deberemos crear el directorio img dentro static y poner allí los ficheros avatar-icon.png y favicon.ico.

Para incorporar el tema oscuro en la plantilla (que no viene de serie), siguiendo este post puedes implementarlo y queda bastante aparente https://yonkov.github.io/post/add-dark-mode-toggle-to-hugo/. Para tener un archivo, he usado este otro post https://www.thedroneely.com/posts/generating-archive-pages-with-hugo/. Finalmente, parece que tarde o temprano tendrás que tirar algo de código porque nunca una plantilla colmará todas tus expectativas.

Si queremos probar nuestro blog en local (y además de hacerlo con hugo server -D, siendo la D de draft) podemos ejecutar hugo --minify (que será lo que ejecutemos después en un workflow en GitHub). Este comando nos generará una carpeta public/ que es ya nuestro blog listo para publicar. Por eso, es buena idea crear un fichero .gitignore y excluir esta carpeta del control de código fuente.

Para crear un nuevo post, la forma más sencilla es hacer uso de los archetypes. En mi caso, he modificado el fichero archetypes/default.md y con la instrucción hugo new post/"my-first-post".md crearé el fichero content\post\my-first-post.md con el título “My First Post”. En la instrucción anterior, yo personalmente le doy el nombre al fichero .md todo en minúsculas y luego cambio la capitalización del título en el post.

---
title: "{{ replace .Name "-" " " | title }}"
date: {{ .Date }}
tags: [ "", "" ]
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla quis erat dui. Curabitur placerat ligula sed augue fermentum porttitor vitae bibendum leo. Donec finibus finibus suscipit. Nunc vel pharetra odio. Nunc tortor est, aliquam ut magna vitae, gravida mollis sapien. In augue nunc, gravida nec ullamcorper at, scelerisque quis nisi. Aenean sollicitudin ipsum commodo ipsum finibus, sit amet fermentum dolor tristique

<!--more-->

Quisque pellentesque ligula orci, eu suscipit eros venenatis id. Sed dui metus, finibus non lorem eget, ultrices porta ipsum. Nullam lobortis pulvinar sem eu accumsan. In hac habitasse platea dictumst. Sed eu odio faucibus, fermentum ipsum vitae, semper odio. Curabitur interdum ullamcorper orci, et gravida sapien hendrerit posuere. Pellentesque tellus nulla, accumsan vitae maximus vel, ultricies eget dolor. Fusce eget ligula laoreet ante rhoncus tristique. Pellentesque efficitur faucibus lectus eu tincidunt.

GitHub

Ya con nuestro blob creado y funcionando es hora de publicarlo en GitHub para que sea accesible de forma pública.

Lo que tendremos que hacer es crear 2 repositorios. El primero será privado (aunque podría ser público) y contendrá el repositorio de Hugo. Puede llamarse de cualquier forma, yo he optado por panicoenlaxbox-blog. Por el contrario el segundo tiene que ser público y obligatoriamente ha de llamarse <tu_nombre_de_usuario_en_github>.github.io. Será en este segundo repositorio donde publiquemos de forma automática la carpeta public/. Por último, hay que crear un token de GitHub con el permiso public_repo y crear un secreto llamado PERSONAL_TOKEN en el primer repositorio.

token_permission

Ahora crearemos en el primer repositorio un workflow .github\workflows\gh-pages.yml que publicará nuestro blog en el repositorio público.

# https://gohugo.io/hosting-and-deployment/hosting-on-github/
name: github pages

on:
  workflow_dispatch:
  push:
    branches:
      - main  # Set a branch to deploy
  pull_request:

jobs:
  deploy:
    runs-on: ubuntu-20.04
    steps:
      - uses: actions/checkout@v2
        with:
          submodules: true  # Fetch Hugo themes (true OR recursive)
          fetch-depth: 0    # Fetch all history for .GitInfo and .Lastmod

      - name: Setup Hugo
        uses: peaceiris/actions-hugo@v2
        with:
          hugo-version: 'latest'
          # extended: true

      - name: Build
        run: hugo --minify

      # https://github.com/peaceiris/actions-gh-pages#%EF%B8%8F-set-personal-access-token-personal_token
      # https://github.com/peaceiris/actions-gh-pages#%EF%B8%8F-deploy-to-external-repository-external_repository
      - name: Deploy
        uses: peaceiris/actions-gh-pages@v3
        if: github.ref == 'refs/heads/main'
        with:
          personal_token: ${{ secrets.PERSONAL_TOKEN }}
          publish_dir: ./public
          external_repository: panicoenlaxbox/panicoenlaxbox.github.io
          publish_branch: main        

Y con esto ya tenemos publicado nuestro blog.

Dominio personalizado

Si además queremos agregar un dominio personalizado, y aunque esté explicado aquí, yo igualmente te dejo un par de imágenes que muestran la configuración que, en mi caso, funcionó después de un par de intentos fallidos.

pages

dns

Después de esto tendrás que actualizar el fichero config.toml con tu nueva baseurl.

Además, al crear el dominio personalizado en GitHub, se añadió un commit automático al repositorio público con un fichero CNAME cuyo contenido es simplemente el valor de tu custom domain. Como no queremos perderlo cada vez que publiquemos, lo más sencillo es generarlo desde el workflow después del paso Build.

      - name: CNAME
        run: 
          echo www.panicoenlaxbox.com >> public/CNAME

Even theme

Aunque no es el caso de Beautiful Hugo, otros temas como por ejemplo Even te recomiendan clonar su repositorio en vez de agregarlo como un submódulo. En ese caso y después de haberlo clonado en local con git clone https://github.com/olOwOlo/hugo-theme-even themes/even, tendrás que añadir a tu GitHub Action lo siguiente para clonarlo también durante la ejecución del workflow.

      - uses: actions/checkout@v2
        ...

      - uses: actions/checkout@v2
        with:
          repository: olOwOlo/hugo-theme-even
          path: themes/even

Fíjate que GitHub te muestra de distinta forma los submódulos y los repositorios clonados.

submodule_and_clone

Y para acabar (y si estás migrando tu blog), puedes usar algo así para redireccionar entradas concretas a tu nuevo blog https://www.freakyjolly.com/how-to-redirect-single-blogger-post-to-external-url/

Para poder incluir en el archivo todos los posts de mi blog anterior, http://panicoenlaxbox.blogspot.com he optado por modificar el fichero layouts\archive\single.html e incluir a mano (después del bloque de código que genera automáticamente el archivo para el blog actual) una lista que he generado con el siguiente script de PowerShell:

# https://developers.google.com/blogger/docs/3.0/using
$apiKey = 'YOUR_API_KEY'
$blog = 'https://panicoenlaxbox.blogspot.com/'
$baseUrl = 'https://www.googleapis.com/blogger/v3/blogs'
$url = "$baseUrl/byurl?url=$blog&key=$apiKey"
Write-Host 'Getting blog id...'
$blogId = (Invoke-RestMethod -Uri $url -Method Get).id
Write-Host "blog id is $blogId"
$url = "$baseUrl/$blogId/posts?key=$apiKey"
Write-Host 'Getting posts...'
$response = Invoke-RestMethod -Uri $url -Method Get
$posts = $response.items | Select-Object published, url, title
$nextPageToken = $response.nextPageToken
while ($null -ne $nextPageToken)
{
    Write-Host "Getting next page with token $nextPageToken..."
    $url = "$baseUrl/$blogId/posts?key=$apiKey&pageToken=$nextPageToken"
    $response = Invoke-RestMethod -Uri $url -Method Get
    $posts += $response.items | Select-Object published, url, title
    $nextPageToken = $response.nextPageToken
}
# $posts | ConvertTo-Json | Out-File "blogger.json"
# $posts = Get-Content -Path "blogger.json" | ConvertFrom-Json
Write-Host 'Grouping posts by year...'
$years = $posts | 
Select-Object `
@{ Name = 'Year'; Expression = { $_.published.Year } }, `
@{ Name = 'Date'; Expression = { $_.published } }, `
@{ Name = 'Url'; Expression = { $_.url } }, `
@{ Name = 'Title'; Expression = { $_.title } } | 
Group-Object -Property Year | Sort-Object -Property Name -Descending
Write-Host 'Generating html...'
$html = ""
foreach ($year in $years)
{
    Write-Host "Generating html for year $($year.Name)"
    $html += "<h1 class='title is-4 has-text-weight-normal'>$($year.Name)</h1><ul class='article__list'>"
    foreach ($post in $year.Group)
    {
        $html += "<li><a class='is-block' href='$($post.Url)'><span class='has-text-grey-dark'>$($post.Date.ToString("dd MMM"))</span> — $($post.Title)</a></li>"
    }
    $html += '</li></ul>'
}
$html | Out-File "blogger.html"

Ahora sí, ¡reto conseguido!.


Ver también