diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml new file mode 100644 index 0000000..3709e07 --- /dev/null +++ b/.gitea/workflows/deploy.yaml @@ -0,0 +1,35 @@ +name: Deploy +on: + push: + branches: + - main + +jobs: + build-and-test: + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v4 + with: + submodules: true + + - name: Check 🔍 + uses: zolacti/on@check + with: + drafts: true + + - name: Build 🛠 + uses: zolacti/on@build + + - name: Deploy 🚀 + uses: appleboy/scp-action@v0.1.7 + with: + host: ${{ vars.ATLAS_SSH_HOST }} + username: ${{ vars.ATLAS_SSH_USERNAME }} + key: ${{ secrets.ATLAS_SSH_KEY }} + port: ${{ vars.ATLAS_SSH_PORT }} + source: public + target: /www/blog + rm: true + overwrite: true + strip_components: 1 diff --git a/.githooks/pre-commit b/.githooks/pre-commit index f1833b6..0325675 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -69,7 +69,8 @@ function has_body_changes() { # Function to update the social media card for a post or section. function generate_and_commit_card { local file=$1 - social_media_card=$(social-cards-zola -o static/img/social_cards -b http://127.0.0.1:1111 -u -p -i "$file") || { + echo "Generating social media card for $file" + social_media_card=$(./static/code/social-cards-zola -o static/img/social_cards -b http://127.0.0.1:1111 -u -p -i "$file") || { echo "Failed to update social media card for $file" exit 1 } @@ -165,19 +166,19 @@ for file in "${all_changed_files[@]}"; do continue fi - # If the file is an .md file and it's a draft, abort the commit. - if [[ "$file" == *.md ]]; then - if is_draft "$file"; then - error_exit "Draft file $file is being committed!" - fi - fi +# # If the file is an .md file and it's a draft, abort the commit. +# if [[ "$file" == *.md ]]; then +# if is_draft "$file"; then +# error_exit "Draft file $file is being committed!" +# fi +# fi done # Use `social-cards-zola` to create/update the social media card for Markdown files. # See https://osc.garden/blog/automating-social-media-cards-zola/ for context. changed_md_files=$(echo "$all_changed_files" | grep '\.md$') -echo "Changed Markdown files: $changed_md_files" # Use parallel to create the social media cards in parallel and commit them. +echo "processing $changed_md_files out of $all_changed_files" if [[ -n "$changed_md_files" ]]; then echo "$changed_md_files" | parallel -j 8 generate_and_commit_card fi diff --git a/.githooks/social-cards-zola b/.githooks/social-cards-zola deleted file mode 100755 index bd92625..0000000 --- a/.githooks/social-cards-zola +++ /dev/null @@ -1,228 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail - -# This script takes a markdown post, crafts the corresponding URL, checks if it's accessible, -# takes a screenshot, and saves it to a specified location. -# It can update the front matter of the post with the path to the generated image (-u | --update-front-matter option). -# It's meant to be used as a pre-commit hook to generate social media cards for Zola sites using the tabi theme. -# More details: https://osc.garden/blog/automating-social-media-cards-zola/ - -function help_function(){ - echo "This script automates the creation of social media cards for Zola websites." - echo "It takes a Markdown post and saves its live screenshot to a specified location." - echo "" - echo "IMPORTANT! It needs to be run from the root of the Zola site." - echo "" - echo "Usage: social-cards-zola [OPTIONS]" - echo "" - echo "Options:" - echo " -h, --help Show this help message and exit." - echo " -b, --base_url URL The base URL where the Zola site is hosted. Default is http://127.0.0.1:1111." - echo " -i, --input INPUT_PATH The relative path to the markdown file of the post/section you want to capture. Should be in the format 'content/blog/post_name.language.md'." - echo " -k, --key KEY The front matter key to update. Default is 'social_media_card'." - echo " -o, --output_path PATH The directory where the generated image will be saved." - echo " -p, --print_output Print the path to the resulting screenshot at the end." - echo " -u, --update-front-matter Update or add the 'social_media_card' key in the front matter of the Markdown file." - echo - echo "Examples:" - echo " social-cards-zola --base_url https://example.com --input content/blog/my_post.md --output_path static/img/social_cards" - echo " social-cards-zola -u -b http://127.0.0.1:1025 -i content/archive/_index.es.md -o static/img" - exit 0 -} - -function convert_filename_to_url() { - # Remove .md extension. - local post_name="${1%.md}" - - # Remove "content/" prefix. - local url="${post_name#content/}" - - # Extract language code. - local lang_code="${url##*.}" - if [[ "$lang_code" == "$url" ]]; then - lang_code="" # No language code. - else - lang_code="${lang_code}/" # Add trailing slash. - url="${url%.*}" # Remove the language code from the URL. - fi - - # Handle co-located index.md by stripping it and using the directory as the URL. - if [[ "$url" == */index ]]; then - url="${url%/*}" # Remove the /index suffix. - fi - - # Remove "_index" suffix. - if [[ "$url" == *"_index"* ]]; then - url="${url%%_index*}" - fi - - # Return the final URL with a single trailing slash. - full_url="${lang_code}${url}" - echo "${full_url%/}/" -} - -function error_exit() { - echo "ERROR: $1" >&2 - exit "${2:-1}" -} - -function validate_input_params() { - missing_params=() - if [[ -z "$base_url" ]]; then - missing_params+=("base_url") - fi - if [[ -z "$input" ]]; then - missing_params+=("input") - fi - if [[ -z "$output_path" ]]; then - missing_params+=("output_path") - fi - - if [ ${#missing_params[@]} -ne 0 ]; then - error_exit "The following required settings are missing: ${missing_params[*]}. Use -h or --help for usage." - fi -} - -function check_dependencies() { - for cmd in "curl" "shot-scraper"; do - if ! command -v $cmd &> /dev/null; then - error_exit "$cmd could not be found. Please install it." - fi - done -} - -function fetch_status() { - local retry_count=0 - local max_retries=5 - local status - while [[ $retry_count -lt $max_retries ]]; do - status=$(curl -s -o /dev/null -I -w "%{http_code}" "${base_url}${post_url}") - if [[ "$status" -eq "200" ]]; then - return - fi - retry_count=$((retry_count + 1)) - sleep 2 - done - error_exit "Post $input is not accessible. Max retries ($max_retries) reached." -} - -function capture_screenshot() { - temp_file=$(mktemp /tmp/social-zola.XXXXXX) - trap 'rm -f "$temp_file"' EXIT - shot-scraper --silent "${base_url}/${post_url}" -w 700 -h 400 --retina --quality 60 -o "$temp_file" -} - -function move_file() { - local safe_filename=$(echo "${post_url%/}" | sed 's/[^a-zA-Z0-9]/_/g') - - # Create the output directory if it doesn't exist. - mkdir -p "$output_path" - - image_filename="${output_path}/${safe_filename:-index}.jpg" # If the filename is empty, use "index". - mv "$temp_file" "$image_filename" || error_exit "Failed to move the file to $image_filename" -} - -function update_front_matter { - local md_file_path="$1" - local image_output="${2#static/}" - # Temporary file for awk processing - temp_awk=$(mktemp /tmp/frontmatter.XXXXXX) - - awk -v card_path="$image_output" ' - # Initialize flags for tracking state. - BEGIN { in_extra=done=front_matter=extra_exists=0; } - - # Function to insert the social_media_card path. - function insert_card() { print "social_media_card = \"" card_path "\""; done=1; } - - { - # If card has been inserted, simply output remaining lines. - if (done) { print; next; } - - # Toggle front_matter flag at its start, denoted by +++ - if (/^\+\+\+/ && front_matter == 0) { - front_matter = 1; - print "+++"; - next; - } - - # Detect [extra] section and set extra_exists flag. - if (/^\[extra\]/) { in_extra=1; extra_exists=1; print; next; } - - # Update existing social_media_card. - if (in_extra && /^social_media_card =/) { insert_card(); in_extra=0; next; } - - # End of front matter or start of new section. - if (in_extra && (/^\[[a-zA-Z_-]+\]/ || (/^\+\+\+/ && front_matter == 1))) { - insert_card(); # Add the missing social_media_card. - in_extra=0; - } - - # Insert missing [extra] section. - if (/^\+\+\+/ && front_matter == 1 && in_extra == 0 && extra_exists == 0) { - print "\n[extra]"; - insert_card(); - in_extra=0; - front_matter = 0; - print "+++"; - next; - } - - # Print all other lines as-is. - print; - }' "$md_file_path" > "$temp_awk" - - # Move the temporary file back to the original markdown file. - mv "$temp_awk" "$md_file_path" -} - -function main() { - while [[ "$#" -gt 0 ]]; do - case "$1" in - -h|--help) - help_function;; - -b|--base_url) - base_url="$2" - shift 2;; - -i|--input) - input="$2" - shift 2;; - -o|--output_path) - output_path="$2" - shift 2;; - -k|--key) - front_matter_key="$2" - shift 2;; - -u|--update-front-matter) - update="true" - shift 1;; - -p|--print_output) - print_output="true" - shift 1;; - *) - error_exit "Unknown option: $1";; - esac - done - - validate_input_params - check_dependencies - - : "${base_url:="http://127.0.0.1:1111"}" - : "${front_matter_key:="social_media_card"}" - base_url="${base_url%/}/" # Ensure one trailing slash. - post_url="$(convert_filename_to_url "$input")" - - fetch_status - capture_screenshot - move_file - - if [[ "$update" == "true" ]]; then - update_front_matter "$input" "$image_filename" - fi - - if [[ "$print_output" == "true" ]]; then - echo "$image_filename" - fi -} - -main "$@" \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f36daff --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +public +.idea diff --git a/.idea/vcs.xml b/.idea/vcs.xml index c592ff1..50105fc 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,5 +1,11 @@ + + + + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..78b7e90 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# My Blog + +Welcome to my blog! Here I write about things I find interesting, like programming, science, and philosophy. + +It's available at [aldofunes.com](https://www.aldofunes.com). \ No newline at end of file diff --git a/config.toml b/config.toml index 4566195..bf2701a 100644 --- a/config.toml +++ b/config.toml @@ -1,39 +1,563 @@ -# The URL the site will be built for +# The base URL of the site; the only required configuration variable. base_url = "https://www.aldofunes.com" -default_language = "en" -build_search_index = true -theme = "tabi" + +# The site title and description; used in feeds by default. title = "Aldo Funes" -taxonomies = [{name = "tags", feed = true}] +description = "Welcome to my personal blog!" + +# The default language; used in feeds. +default_language = "en" + +# The site theme to use. +theme = "tabi" + +# For overriding the default output directory `public`, set it to another value (e.g.: "docs") +output_dir = "public" + +# Whether dotfiles at the root level of the output directory are preserved when (re)building the site. +# Enabling this also prevents the deletion of the output folder itself on rebuilds. +preserve_dotfiles_in_output = false + +# When set to "true", the Sass files in the `sass` directory in the site root are compiled. +# Sass files in theme directories are always compiled. +compile_sass = false + +# When set to "true", the generated HTML files are minified. +minify_html = true + +# A list of glob patterns specifying asset files to ignore when the content +# directory is processed. Defaults to none, which means that all asset files are +# copied over to the `public` directory. +# Example: +# ignored_content = ["*.{graphml,xlsx}", "temp.*", "**/build_folder"] +ignored_content = [] + +# Similar to ignored_content, a list of glob patterns specifying asset files to +# ignore when the static directory is processed. Defaults to none, which means +# that all asset files are copied over to the `public` directory +ignored_static = [] + +# When set to "true", a feed is automatically generated. +generate_feeds = true + +# When set to "all", paginated pages are not a part of the sitemap, default is "none" +exclude_paginated_pages_in_sitemap = "none" + +# The filenames to use for the feeds. Used as the template filenames, too. +# Defaults to ["atom.xml"], which has a built-in template that renders an Atom 1.0 feed. +# There is also a built-in template "rss.xml" that renders an RSS 2.0 feed. +feed_filenames = ["atom.xml"] + +# The number of articles to include in the feed. All items are included if +# this limit is not set (the default). +# feed_limit = 20 + +# When set to "true", files in the `static` directory are hard-linked. Useful for large +# static files. Note that for this to work, both `static` and the +# output directory need to be on the same filesystem. Note that the theme's `static` +# files are always copied, regardless of this setting. +hard_link_static = false + +# The default author for pages +author = "Aldo Funes" + +# The taxonomies to be rendered for the site and their configuration of the default languages +# Example: +# taxonomies = [ +# {name = "tags", feed = true}, # each tag will have its own feed +# {name = "tags"}, # you can have taxonomies with the same name in multiple languages +# {name = "categories", paginate_by = 5}, # 5 items per page for a term +# {name = "authors"}, # Basic definition: no feed or pagination +# ] +# +taxonomies = [{ name = "tags" }] + +# When set to "true", a search index is built from the pages and section +# content for `default_language`. +build_search_index = true + +# When set to "false", Sitemap.xml is not generated +generate_sitemap = true + +# When set to "false", robots.txt is not generated +generate_robots_txt = true + +# Configuration of the Markdown rendering +[markdown] +# When set to "true", all code blocks are highlighted. +highlight_code = true + +# When set to "true", missing highlight languages are treated as errors. Defaults to false. +error_on_missing_highlight = false + +# A list of directories used to search for additional `.sublime-syntax` and `.tmTheme` files. +extra_syntaxes_and_themes = [] + +# To use a Zola built-in theme, CSP needs to allow unsafe-inline for style-src. +highlight_theme = "css" +highlight_themes_css = [{ theme = "dracula", filename = "css/syntax.css" }] + +# When set to "true", emoji aliases translated to their corresponding +# Unicode emoji equivalent in the rendered Markdown files. (e.g.: :smile: => 😄) +render_emoji = false + +# CSS class to add to external links (e.g. "external-link") +external_links_class = "external" + +# Whether external links are to be opened in a new tab +# If this is true, a `rel="noopener"` will always automatically be added for security reasons +external_links_target_blank = true + +# Whether to set rel="nofollow" for all external links +external_links_no_follow = false + +# Whether to set rel="noreferrer" for all external links +external_links_no_referrer = false + +# Whether smart punctuation is enabled (changing quotes, dashes, dots in their typographic form) +# For example, `...` into `…`, `"quote"` into `“curly”` etc +smart_punctuation = true + +# Whether parsing of definition lists is enabled +definition_list = false + +# Whether to set decoding="async" and loading="lazy" for all images +# When turned on, the alt text must be plain text. +# For example, `![xx](...)` is ok but `![*x*x](...)` isn’t ok +lazy_async_image = false + +# Whether footnotes are rendered in the GitHub-style (at the bottom, with back references) or plain (in the place, where they are defined) +bottom_footnotes = true + +# This determines whether to insert a link for each header like the ones you can see on this site if you hover over +# a header. +# The default template can be overridden by creating an `anchor-link.html` file in the `templates` directory. +# This value can be "left", "right", "heading" or "none". +# "heading" means the full heading becomes the text of the anchor. +# See "Internal links & deep linking" in the documentation for more information. +insert_anchor_links = "none" + +# Configuration of the link checker. +[link_checker] +# Skip link checking for external URLs that start with these prefixes +skip_prefixes = [ + "http://[2001:db8::]/", +] + +# Skip anchor checking for external URLs that start with these prefixes +skip_anchor_prefixes = [ + "https://caniuse.com/", +] + +# Treat internal link problems as either "error" or "warn", default is "error" +internal_level = "error" + +# Treat external link problems as either "error" or "warn", default is "error" +external_level = "error" + +# Various slugification strategies, see below for details +# Defaults to everything being a slug +[slugify] +paths = "on" +taxonomies = "on" +anchors = "on" +# Whether to remove date prefixes for page path slugs. +# For example, content/posts/2016-10-08_a-post-with-dates.md => posts/a-post-with-dates +# When true, content/posts/2016-10-08_a-post-with-dates.md => posts/2016-10-08-a-post-with-dates +paths_keep_dates = false + +[search] +# Whether to include the title of the page/section in the index +include_title = true +# Whether to include the description of the page/section in the index +include_description = false +# Whether to include the RFC3339 datetime of the page in the search index +include_date = false +# Whether to include the path of the page/section in the index (the permalink is always included) +include_path = false +# Whether to include the rendered content of the page/section in the index +include_content = true +# At which code point to truncate the content to. Useful if you have a lot of pages and the index would +# become too big to load on the site. Defaults to not being set. +# truncate_content_length = 100 + +# Whether to produce the search index as a javascript file or as a JSON file +# Accepted values: +# - "elasticlunr_javascript", "elasticlunr_json" +# - "fuse_javascript", "fuse_json" +index_format = "elasticlunr_json" + +# Optional translation object for the default language +# Example: +# default_language = "fr" +# +# [translations] +# title = "Un titre" +# +[translations] + +# Additional languages definition +# You can define language specific config values and translations: +# title, description, generate_feeds, feed_filenames, taxonomies, build_search_index +# as well as its own search configuration and translations (see above for details on those) +[languages] +# For example +# [languages.fr] +# title = "Mon blog" +# generate_feeds = true +# taxonomies = [ +# {name = "auteurs"}, +# {name = "tags"}, +# ] +# build_search_index = false [languages.es] title = "Aldo Funes" -taxonomies = [{name = "tags", feed = true}] - -[languages.pt-PT] -title = "Aldo Funes" -taxonomies = [{name = "tags", feed = true}] - -[markdown] -# Whether to do syntax highlighting -# Theme can be customised by setting the `highlight_theme` variable to a theme supported by Zola -highlight_code = true -#highlight_theme = "dracula" -highlight_theme = "css" -highlight_themes_css = [ - { theme = "dracula", filename = "css/syntax.css" }, -] -external_links_class = "external" +generate_feeds = true +taxonomies = [{ name = "tags" }] +build_search_index = true +#[languages.pt-PT] +#title = "Aldo Funes" +#generate_feeds = true +#taxonomies = [{ name = "tags" }] +#build_search_index = true [extra] -# Put all your custom variables here +# Check out the documentation (or the comments below) to learn how to customise tabi: +# https://welpo.github.io/tabi/blog/mastering-tabi-settings/ + +# Use sans-serif font everywhere. +# By default, the serif font is only used in articles. +override_serif_with_sans = false + +# Enable JavaScript theme toggler to allow users to switch between dark/light mode. +# If disabled, your site will use the theme specified in the `default_theme` variable. +theme_switcher = true + +# This setting determines the default theme on load ("light" or "dark"). +# To follow the user's OS theme, leave it empty or unset. +default_theme = "" + +# Choose the colourscheme (skin) for the theme. Default is "teal". +# Skin available: blue, lavender, mint, red, sakura, teal, monochrome, lowcontrast_orange, lowcontrast_peach, lowcontrast_pink, indigo_ingot, evangelion +# See them live and learn how to create your own: https://welpo.github.io/tabi/blog/customise-tabi/#skins +# WARNING! "lowcontrast" skins, while aesthetically pleasing, may not provide optimal +# contrast (in light theme) for readability and might not be suitable for all users. +# Furthermore, low contrasting elements will affect your Google Lighthouse rating. +# All other skins have optimal contrast. +skin = "" + +# Set browser theme colour. Can be a single colour or [light, dark]. +# Note: Bright colors may be ignored in dark mode. +# More details: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name/theme-color +# browser_theme_color = "#087e96" # Example of single value. +# browser_theme_color = ["#ffffff", "#000000"] # Example of light/dark colours. + +# List additional stylesheets to load site-wide. +# These stylesheets should be located in your site's `static` directory. +# Example: stylesheets = ["extra1.css", "path/extra2.css"] +# You can load a stylesheet for a single post by adding it to the [extra] section of the post's front matter, following this same format. stylesheets = ["css/syntax.css"] -remote_repository_url = "https://github.com/aldofunes/blog" -remote_repository_git_platform = "auto" -remote_repository_branch = "main" -show_remote_changes = true -show_remote_source = true +# Sets the default canonical URL for all pages. +# Individual pages can override this in the [extra] section using canonical_url. +# Example: "$base_url/blog/post1" will get the canonical URL "https://example.com/blog/post1". +# Note: To ensure accuracy in terms of matching content, consider setting 'canonical_url' individually per page. +# base_canonical_url = "https://example.com" -favicon_emoji = "👾" \ No newline at end of file +# Remote repository for your Zola site. +# Used for `show_remote_changes` and `show_remote_source` (see below). +# Supports GitHub, GitLab, Gitea, and Codeberg. +remote_repository_url = "https://gitea.funes.me/aldo/blog" +# Set this to "auto" to try and auto-detect the platform based on the repository URL. +# Accepted values are "github", "gitlab", "gitea", and "codeberg". +remote_repository_git_platform = "gitea" # Defaults to "auto". +# Branch in the repo hosting the Zola site. +remote_repository_branch = "main" # Defaults to "main". +# Show a link to the commit history of updated posts, right next to the last updated date. +# Can be set at page or section levels, following the hierarchy: page > section > config. See: https://welpo.github.io/tabi/blog/mastering-tabi-settings/#settings-hierarchy +show_remote_changes = true # Defaults to true. +# Show a link to the repository of the site, right next to the "Powered by Zola & tabi" text. +show_remote_source = true # Defaults to true. +# Add a "copy" button to codeblocks (loads ~700 bytes of JavaScript). +# Can be set at page or section levels, following the hierarchy: page > section > config. See: https://welpo.github.io/tabi/blog/mastering-tabi-settings/#settings-hierarchy +copy_button = true + +# Make code block names clickable if they are URLs (loads ~400 bytes of JavaScript). +# Can be set at page or section levels, following the hierarchy: page > section > config. See: https://welpo.github.io/tabi/blog/mastering-tabi-settings/#settings-hierarchy +code_block_name_links = false + +# Force left-to-right (LTR) direction for code blocks. +# Set to false to allow code to follow the document's natural direction. +# Can be set at page or section levels. See: https://welpo.github.io/tabi/blog/mastering-tabi-settings/#settings-hierarchy +force_codeblock_ltr = true + +# Show the author(s) of a page. +# Can be set at page or section levels, following the hierarchy: page > section > config. See: https://welpo.github.io/tabi/blog/mastering-tabi-settings/#settings-hierarchy +show_author = false + +# Show the reading time of a page. +# Can be set at page or section levels, following the hierarchy: page > section > config. See: https://welpo.github.io/tabi/blog/mastering-tabi-settings/#settings-hierarchy +show_reading_time = true + +# Show the date of a page below its title. +# Can be set at page or section levels, following the hierarchy: page > section > config. See: https://welpo.github.io/tabi/blog/mastering-tabi-settings/#settings-hierarchy +show_date = true + +# Determines how dates are displayed in the post listing (e.g. front page or /blog). Options: +# "date" - Show only the original date of the post (default if unset). +# "updated" - Show only the last updated date of the post. If there is no last updated date, it shows the original date. +# "both" - Show both the original date and the last updated date. +post_listing_date = "date" + +# Show "Jump to posts" link next to series' title. +# By default, the link appears automatically when a series description exceeds 2000 characters. +# Can be set at page or section levels, following the hierarchy: page > section > config. See: https://welpo.github.io/tabi/blog/mastering-tabi-settings/#settings-hierarchy +# show_jump_to_posts = true + +# Determines if indexes should be increasing (false) or decreasing (true) in series' posts list. +# It has only effect if the section uses indexes metadata (which is only the case for series as of now). +# Can be set at section levels, following the hierarchy: section > config. See: https://welpo.github.io/tabi/blog/mastering-tabi-settings/#settings-hierarchy +post_listing_index_reversed = false # Defaults to false. + +# Enable KaTeX for all posts. +# Can be set at page or section levels, following the hierarchy: page > section > config. See: https://welpo.github.io/tabi/blog/mastering-tabi-settings/#settings-hierarchy +katex = false + +# Enable Mermaid diagrams for all posts. +# Loads ~2.5MB of JavaScript. +# Can be set at page or section levels, following the hierarchy: page > section > config. See: https://welpo.github.io/tabi/blog/mastering-tabi-settings/#settings-hierarchy +mermaid = true + +# Serve Mermaid JavaScript locally. Version bundled with tabi. +# If set to false, it will load the latest version from JSDelivr. +# Only relevant when `mermaid = true`. +serve_local_mermaid = true + +# Show links to previous and next articles at the bottom of posts. +# Can be set at page or section levels, following the hierarchy: page > section > config. See: https://welpo.github.io/tabi/blog/mastering-tabi-settings/#settings-hierarchy +show_previous_next_article_links = true + +# Invert order of the links to previous and next articles at the bottom of posts. +# By default, next articles are on the left side of the page and previous articles are on the right side. +# To reverse the order (next articles on the right and previous articles on the left), set it to true. +# Can be set at page or section levels, following the hierarchy: page > section > config. See: https://welpo.github.io/tabi/blog/mastering-tabi-settings/#settings-hierarchy +invert_previous_next_article_links = false + +# Whether the navigation for previous/next article should match the full width of the site (same as the navigation bar at the top) or the article width. +# To match the navigation bar at the top, set it to true. +previous_next_article_links_full_width = true + +# Quick navigation buttons. +# Adds "go up" and "go to comments" buttons on the bottom right (hidden for mobile). +# Can be set at page or section levels, following the hierarchy: page > section > config. See: https://welpo.github.io/tabi/blog/mastering-tabi-settings/#settings-hierarchy +quick_navigation_buttons = true + +# Add a Table of Contents to posts, right below the title and metadata. +# Can be set at page or section levels, following the hierarchy: page > section > config. See: https://welpo.github.io/tabi/blog/mastering-tabi-settings/#settings-hierarchy +toc = true + +# Date format used when listing posts (main page, /blog section, tag posts list…) +# Default is "6th July 2049" in English and "%d %B %Y" in other languages. +# long_date_format = "%d %B %Y" + +# Date format used for blog posts. +# Default is "6th July 2049" in English and "%-d %B %Y" in other languages. +short_date_format = "" + +# Custom separator used in title tag and posts metadata (between date, time to read, and tags). +separator = "•" + +# Use a shorter layout for All tags listing. +# Default: tag_name – n post[s] +# Compact: tag_name^n (superscript number) +compact_tags = false + +# How tags are sorted in a Tags listing based on templates/tags/list.html. +# "name" for alphabetical, "frequency" for descending count of posts. +# Default: "name". +tag_sorting = "name" + +# Show clickable tags above cards.html template (e.g. projects/) to filter the displayed items. +# Loads JS to filter. If JS is disabled, the buttons are links to the tag's page. +# Can be set at the section or config.toml level, following the hierarchy: section > config. See: https://welpo.github.io/tabi/blog/mastering-tabi-settings/#settings-hierarchy +# Default: true +enable_cards_tag_filtering = true + +# Invert the order of the site title and page title in the browser tab. +# Example: true => "Blog • ~/tabi", false => "~/tabi • Blog" +invert_title_order = false + +# Full path after the base URL required. So if you were to place it in "static" it would be "/favicon.ico" +# favicon = "" + +# Add an emoji here to use it as favicon. +# Compatibility: https://caniuse.com/link-icon-svg +favicon_emoji = "👾" + +# Path to the fallback image for social media cards (the preview image shown when sharing a link on WhatsApp, LinkedIn…). +# Can be set at page or section levels, following the hierarchy: page > section > config. See: https://welpo.github.io/tabi/blog/mastering-tabi-settings/#settings-hierarchy +# Learn how to create these images in batch and automatically: +# https://osc.garden/blog/automating-social-media-cards-zola/ +# social_media_card = "" + +menu = [ + { name = "about", url = "about", trailing_slash = true }, + { name = "blog", url = "blog", trailing_slash = true }, + { name = "archive", url = "archive", trailing_slash = true }, + { name = "tags", url = "tags", trailing_slash = true }, + { name = "categories", url = "categories", trailing_slash = true }, + { name = "projects", url = "projects", trailing_slash = true }, +] + +# The RSS icon will be shown if (1) it's enabled and (2) the following variable is set to true. +# Note for Zola 0.19.X users: when `feed_filenames` has two filenames, only the first one will be linked in the footer. +feed_icon = true + +# Show the full post content in the Atom feed. +# If it's set to false, only the description or summary will be shown. +full_content_in_feed = true + +# Email address for footer's social section. +# Protect against spambots: +# 1. Use base64 for email (convert at https://www.base64encode.org/ or `printf 'your@email.com' | base64`). +# 2. Or, set 'encode_plaintext_email' to true for auto-encoding (only protects on site, not in public repos). +email = "YWxkb0BmdW5lcy5tZQ==" +# Decoding requires ~400 bytes of JavaScript. If JS is disabled, the email won't be displayed. +encode_plaintext_email = true # Setting is ignored if email is already encoded. + +# Social media links for the footer. +# Built-in icons: https://github.com/welpo/tabi/tree/main/static/social_icons +# To use a custom icon, add it to your site's `static/social_icons` directory. +socials = [ + { name = "github", url = "https://github.com/aldofunes/", icon = "github" }, + { name = "gitea", url = "https://gitea.funes.me/aldo", icon = "gitea" }, + { name = "linkedin", url = "https://www.linkedin.com/in/aldo-funes", icon = "linkedin" }, + { name = "mastodon", url = "https://techhub.social/@aldofunes", icon = "mastodon" }, +] + +# Fediverse profile. +# Adds metadata to feature the author's profile in Mastodon link previews. +# Example: for @username@example.com, use: +fediverse_creator = { handle = "aldofunes", domain = "techhub.social" } + +# Extra menu to show on the footer, below socials section. +footer_menu = [ + { url = "https://cal.com/aldofunes", name = "calendar", trailing_slash = false }, + { url = "sitemap.xml", name = "sitemap", trailing_slash = false }, +] + +# Enable a copyright notice for the footer, shown between socials and the "Powered by" text. +# $TITLE will be replaced by the website's title. +# $CURRENT_YEAR will be replaced by the current year. +# $AUTHOR will be replaced by the `author` variable. +# $SEPARATOR will be replaced by the `separator` variable. +# Markdown is supported (links, emphasis, etc). +# copyright = "$TITLE © $CURRENT_YEAR $AUTHOR $SEPARATOR Unless otherwise noted, the content in this website is available under the [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) license." + +# For multi-language sites, you can set a different copyright for each language. +# The old way of setting `translated_copyright = true` and using i18n files is deprecated. +# If a translation is missing for language, the `copyright` value will be used. +# copyright_translations.es = "$TITLE © $CURRENT_YEAR $AUTHOR $SEPARATOR A menos que se indique lo contrario, el contenido de esta web está disponible bajo la licencia [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)." + +# Custom security headers. What urls should your website be able to connect to? +# You need to specify the CSP and the URLs associated with the directive. +# Useful if you want to load remote content safely (embed YouTube videos, which needs frame-src, for example). +# Default directive is self. +# Default config, allows for https remote images and embedding YouTube and Vimeo content. +# This configuration (along with the right webserver settings) gets an A+ in Mozilla's Observatory: https://observatory.mozilla.org +# Note: to use a Zola built-in syntax highlighting theme, allow unsafe-inline for style-src. +allowed_domains = [ + { directive = "font-src", domains = ["'self'", "data:"] }, + { directive = "img-src", domains = ["'self'", "https://*", "data:"] }, + { directive = "media-src", domains = ["'self'"] }, + { directive = "script-src", domains = ["'self'"] }, + { directive = "style-src", domains = ["'self'"] }, + { directive = "frame-src", domains = ["player.vimeo.com", "https://www.youtube-nocookie.com"] }, +] + +# Enable the CSP directives configured (or default). +# Can be set at page or section levels, following the hierarchy: page > section > config. See: https://welpo.github.io/tabi/blog/mastering-tabi-settings/#settings-hierarchy +enable_csp = true + +# Custom subset of characters for the header. +# If set to true, the `static/custom_subset.css` file will be loaded first. +# This avoids a flashing text issue in Firefox. +# Please see https://welpo.github.io/tabi/blog/custom-font-subset/ to learn how to create this file. +custom_subset = true + +[extra.analytics] +# Specify which analytics service you want to use. +# Supported options: ["goatcounter", "umami", "plausible"] +service = "plausible" + +# Unique identifier for tracking. +# For GoatCounter, this is the code you choose during signup. +# For Umami, this is the website ID. +# For Plausible, this is the domain name (e.g. "example.com"). +# Note: Leave this field empty if you're self-hosting GoatCounter. +id = "funes.me" + +# Optional: Specify the URL for self-hosted analytics instances. +# For GoatCounter: Base URL like "https://stats.example.com" +# For Umami: Base URL like "https://umami.example.com" +# For Plausible: Base URL like "https://plausible.example.com" +# Leave this field empty if you're using the service's default hosting. +self_hosted_url = "https://plausible.funes.me" + +# giscus support for comments. https://giscus.app +# Setup instructions: https://welpo.github.io/tabi/blog/comments/#setup +[extra.giscus] +# enabled_for_all_posts = false # Enables giscus on all posts. It can be enabled on individual posts by setting `giscus = true` in the [extra] section of a post's front matter. +# automatic_loading = true # If set to false, a "Load comments" button will be shown. +# repo = "welpo/tabi-comments" +# repo_id = "R_kgDOJ59Urw" # Find this value in https://giscus.app/ +# category = "Announcements" +# category_id = "DIC_kwDOJ59Ur84CX0QG" # Find this value in https://giscus.app/ +# mapping = "slug" # Available: pathname; url; title; slug. "slug" will use the post's filename (slug); this is the only way to share comments between languages. +# strict_title_matching = 1 # 1 to enable, 0 to disable. https://github.com/giscus/giscus/blob/main/ADVANCED-USAGE.md#data-strict +# enable_reactions = 1 # 1 to enable, 0 to disable. +# comment_box_above_comments = false +# light_theme = "noborder_light" +# dark_theme = "noborder_dark" +# lang = "" # Leave blank to match the page's language. +# lazy_loading = true + +# utterances support for comments. https://utteranc.es +# Setup instructions: https://welpo.github.io/tabi/blog/comments/#setup +[extra.utterances] +# enabled_for_all_posts = false # Enables utterances on all posts. It can be enabled on individual posts by setting `utterances = true` in the [extra] section of a post's front matter. +# automatic_loading = true # If set to false, a "Load comments" button will be shown. +# repo = "yourGithubUsername/yourRepo" # https://utteranc.es/#heading-repository +# issue_term = "slug" # Available: pathname; url; title; slug. "slug" will use the post's filename (slug); this is the only way to share comments between languages. https://utteranc.es/#heading-mapping +# label = "💬" # https://utteranc.es/#heading-issue-label +# light_theme = "github-light" # https://utteranc.es/#heading-theme +# dark_theme = "photon-dark" # https://utteranc.es/#heading-theme +# lazy_loading = true + +# Hyvor Talk support for comments. https://talk.hyvor.com +[extra.hyvortalk] +# enabled_for_all_posts = false # Enables hyvortalk on all posts. It can be enabled on individual posts by setting `hyvortalk = true` in the [extra] section of a post's front matter. +# automatic_loading = true # If set to false, a "Load comments" button will be shown. +# website_id = "1234" +# page_id_is_slug = true # If true, it will use the post's filename (slug) as id; this is the only way to share comments between languages. If false, it will use the entire url as id. +# lang = "" # Leave blank to match the page's language. +# page_author = "" # Email (or base64 encoded email) of the author. +# lazy_loading = true + +# Isso support for comments. https://isso-comments.de/ +# You need to self-host the backend first: https://blog.phusion.nl/2018/08/16/isso-simple-self-hosted-commenting-system/ +# More info on some settings: https://isso-comments.de/docs/reference/client-config/ +[extra.isso] +# enabled_for_all_posts = false # Enables Isso on all posts. It can be enabled on individual posts by setting `isso = true` in the [extra] section of a post's front matter. +# automatic_loading = true # If set to false, a "Load comments" button will be shown. +# endpoint_url = "" # Accepts relative paths like "/comments/" or "/isso/", as well as full urls like "https://example.com/comments/". Include the trailing slash. +# page_id_is_slug = true # If true, it will use the relative path for the default language as id; this is the only way to share comments between languages. If false, it will use the entire url as id. +# lang = "" # Leave blank to match the page's language. +# max_comments_top = "inf" # Number of top level comments to show by default. If some comments are not shown, an “X Hidden” link is shown. +# max_comments_nested = "5" # Number of nested comments to show by default. If some comments are not shown, an “X Hidden” link is shown. +# avatar = true +# voting = true +# page_author_hashes = "" # hash (or list of hashes) of the author. +# lazy_loading = true # Loads when the comments are in the viewport (using the Intersection Observer API). \ No newline at end of file diff --git a/content/_index.es.md b/content/_index.es.md index d1f2c5f..67d54d2 100644 --- a/content/_index.es.md +++ b/content/_index.es.md @@ -1,12 +1,15 @@ +++ -title = "Home" +title = "Inicio" # Note we're not setting `paginate_by` here. [extra] -header = { title = "Hola! I'm aldo", img = "img/main.webp", img_alt = "Óscar Fernández, the theme's author" } +header = { title = "¡Hola! Soy Aldo", img = "img/profile.jpg", img_alt = "Aldo Funes, the blog's author" } #section_path = "blog/_index.md" # Where to find your posts. #max_posts = 5 # Show 5 posts on the home page. +++ -# Welcome to your new blog! \ No newline at end of file +# ¡Bienvenido a mi blog! + +Aquí escribo sobre cualquier cosa que me parezca interesante. Este blog es una forma de compartir mis pensamientos y experiencias con el +mundo. Espero que disfrutes leyéndolo tanto como yo disfruto escribiéndolo. diff --git a/content/_index.md b/content/_index.md index a8200cb..4bc5dc4 100644 --- a/content/_index.md +++ b/content/_index.md @@ -3,10 +3,13 @@ title = "Home" # Note we're not setting `paginate_by` here. [extra] -header = { title = "Hello! I'm aldo", img = "img/main.webp", img_alt = "Óscar Fernández, the theme's author" } +header = { title = "Welcome to my blog!", img = "img/profile.jpg", img_alt = "Aldo Funes, the blog's author" } -#section_path = "blog/_index.md" # Where to find your posts. -#max_posts = 5 # Show 5 posts on the home page. +section_path = "blog/_index.md" # Where to find your posts. +max_posts = 5 # Show 5 posts on the home page. +++ -# Welcome to your new blog! \ No newline at end of file +# Welcome to my blog! + +Here, I write about pretty much anything that I find interesting. This blog is a way for me to share my thoughts and experiences with the +world. I hope you enjoy reading it as much as I enjoy writing it. \ No newline at end of file diff --git a/content/archive/_index.md b/content/archive/_index.md index b367200..326515b 100644 --- a/content/archive/_index.md +++ b/content/archive/_index.md @@ -1,4 +1,4 @@ +++ title = "Archive" -#template = "archive.html" +template = "archive.html" +++ \ No newline at end of file diff --git a/content/blog/2018-04-01-why-i-quit-social-networks.md b/content/blog/2018-04-01-why-i-quit-social-networks.md new file mode 100644 index 0000000..9cde378 --- /dev/null +++ b/content/blog/2018-04-01-why-i-quit-social-networks.md @@ -0,0 +1,25 @@ ++++ +title = "Why I quit social networks" +description = "I deleted all my social network accounts 4 months ago. It feels oddly liberating." +date = 2018-04-01 +updated = 2025-03-12 + +[taxonomies] +tags = ["Social Networks", "Life Decisions"] + +[extra] +social_media_card = "img/social_cards/blog_why_i_quit_social_networks.jpg" ++++ + +There was a smart guy called _Dunbar_. He studied some animals and came to the conclusion that a person's social circle is limited to its +brain's capacity to store information. In other words, we can only store so many individuals in our brain, the rest is just people. It may +even sound mean, but tu us, people are outside our caring boundaries. This fella studied several poeple and decided that our limit is 150. + +What? We can only remember 150 people? But hey, I have like 1,000+ friends in Facebook alone! That cannot be true! + +It is a little bit more complicated than that. Knowing someone is different than caring for her. Let's do a quick exercise, grab a pen and +paper, and (without looking to your Facebook) make a list of everyone you know. You may have super-memory, and I can still guarantee that +you will struggle to write 50 names, let alone 150. This number is much lower in practice. + +So, what does this have to do with quitting social networks? Well, I realized that I was spending too much time on them. I am not a social +person. Actually, you may define me as introvert. So, being introverted diff --git a/content/blog/2018-07-05-expedition-to-peru.md b/content/blog/2018-07-05-expedition-to-peru.md new file mode 100644 index 0000000..ee1dc94 --- /dev/null +++ b/content/blog/2018-07-05-expedition-to-peru.md @@ -0,0 +1,207 @@ ++++ +title = "Expedition to Peru" +description = "A mountaineering expedition to the Cordillera Blanca in Peru." +date = 2018-07-05 + +[taxonomies] +tags = ["alpinism", "expeditions"] ++++ + +Nuestro vuelo salió de México el miércoles. Hicimos una escala de unas dos horas en el aeropuerto de +El Salvador antes de tomar el vuelo que nos llevó a Lima. Saliendo del aeropuerto, como a las 2:00 +AM tomamos un Uber que nos llevó a un hostal cerca de la terminal de camiones, donde logramos +dormir poco más de tres horas. + + + +Después de un buen baño, nos recogió el Uber con dirección a la terminal de Cruz del Sur, a 4 km. +del hostal. Ahí empezaron los problemas. La hora pico en Lima resultó ser igual de problemática que +la de la Ciudad de México. Bajando del uber, corrimos a la terminal, pues el camión salía 9:30 y ya +eran las 9:25 y no habíamos registrado ni pagado las maletas. Al final, el camión venía retrasado y +lo tuvimos que esperar, ya más relajados. + +El viaje fue muy cómodo, como si hubiéramos viajado en avión de primera clase; comida y bebida a +bordo, y unos asientos increíblemente cómodos. En las ocho horas de camino me dio tiempo de leer, +dormir, comer, escuchar música, ver películas, dormir un poco más y ver el paisaje de la costa de +peruana. + +Nos recogieron a las ocho de la mañana y tres horas más tarde llegamos a Cashapampa. La expedición +iniciamos con: + +- Andrea +- Manuel +- Aldo +- Nehemio (El Demonio): guía +- Hernán (Huaypa): segundo guía +- Michel (Chara): cocinero +- Ezequiel (Binglis): porteador +- Álvaro: arriero +- Juanito: chofer + +La caminata a Llamacorral nos tomó más a nosotros que a los burros con todas nuestras cosas que +usaríamos en los campos base. Normalmente en una expedición que organizamos nosotros, para este +momento llevamos una barrita, unas nueces y fruta deshidratada; comer comida real suele ser un lujo. +Sin embargo, Chara nos dio, a mitad de camino, una pieza de pollo asado con un poco de arroz verde. +En el campamento de Llamacorral tomamos el té, comimos un poco de pan y platicamos un rato. De vez +en cuando se escuchaba a Chara gritar _¡Aldo, chiste!_, a lo que tenía que responder con uno bueno +si quería tener derecho a cena. + +Es buen momento para aclarar nuestro plan. El itinerario pactado con Nehemio quedó así: + +1. Huaraz - Llamacorral +1. Llamacorral - Campo base del Alpamayo +1. Campo base - Campo alto +1. Campo alto - Cumbre del Alpamayo - Campo Alto +1. Campo alto - Cumbre del Quitaraju - Campo Alto +1. Campo alto - Campo base +1. Campo base - Campo base del Artesonraju +1. Campo base de Artesonraju - Campo morrena +1. Campo morrena - Cumbre del Artesonraju - Campo base +1. Campo base - Huaraz + +Estando en el campo base del Alpamayo, nos enteramos de que el Alpamayo tenía, sobre la canaleta de +la ruta, unas cornisas enormes que podían caer en cualquier momento y convertirse en una avalancha +letal. Después, escuchamos de otro guía que intentó en Quitaraju, que se tuvo que bajar en el sexto +largo (de 10) porque la nieve estaba muy suelta y no permitía poner protecciones. + +Hubo un accidente en el campo alto. Un alemán, tratando de recuperar una botella de agua, se cayó en +una grieta en el glaciar del campo alto. La verdad es que fue un acto estúpido, pero se golpeó la +cabeza y se rompió el fémur. La policía de montaña de Huaraz, según los guías, siempre se roban el +crédito de rescates como este. Los primeros en llegar son el cuerpo de rescate de la casa de guías, +y los policías llegan, se toman una foto y reportan que _después de X horas, rescataron a X +personas_. + +Ya descartamos el Alpamayo, porque el riesgo es demasiado alto. Esperaremos a bajar para tener +noticias del Quitaraju para ver si vale la pena subir al campo alto. Mientras, la caminata de 6 +horas al campo morrena del Artesonraju nos mantendrá ocupados. Hicimos una rápida parada en el +campamento base del Artesonraju para comer un poco y armar las mochilas con las que subiremos. +Ahora ya no tendremos burros que nos ayuden, por lo que cada gramo cuenta. + +La subida es muy empinada y hace mucho calor. Subimos lento por las faldas de la montaña. +Además,Manu y yo llevamos nuestra cámara, dos lentes y un tripié cada quien. Nos consuela pensar en +las fotos de estrellas que vamos a tomar. Mirar al cielo nocturno y ver la Vía Láctea es una de las +experiencias más impresionantes de ir a una montaña. La altitud y la ausencia de contaminación +lumínica hacen que el cielo aparezca como en una película. La cantidad de estrellas que se ven me +hacen pensar en las civilizaciones antiguas, que basaban su vida en ellas. + +_!Manu, chiste!_ Ahora le tocaba a Manuel contarle un chiste a Chara. Nos preparó una cena bastante +buena para estar a 5,000 metros sobre el nivel del mar. Cuando viajamos solos, para ahorrar peso, +llevamos bolsas de comida deshidratada; algo así como comida de astronauta. Hay de varias cosas: +lasagna bolognesa, arroz con carne, pollo thai, incluso hay postres como pie de queso. Esta vez +cenamos una sopita de verduras y un poco de pasta con salsa de jitomate. Es muy extraño ver en un +campamento así, una olla exprés; pero sino, por alguna razón Chara no se sentiría cómodo hirviendo +papas. + +Nos despertamos a las once de la noche. Hicimos los últimos preparativos. Tomamos un rápido té para +entrar en calor. Nos equipamos y salimos con ansias de subir una de las montañas más técnicas de +Perú. Dos horas de morrena, tres de glaciar y cuatro de paredes de hielo; más o menos eso vamos a +hacer. La morrena se pasó rápido, el glaciar no estaba nada complicado. Nuestro sufrimiento empezó +cuando alcanzamos a los argentinos que habíamos conocido el día anterior. + +Una de ellos era guía en el Aconcagua, que puede llegar a cuarenta bajo cero. El Arteson, con sus +diez bajo cero no presentaba un reto en cuanto a la temperatura para ellos. Salieron poco antes que +nosotros con la intención de que nuestro paso más lento no fuera problemático para nadie. + +Una hora los tuvimos que esperar poco antes de iniciar el primer largo en la pared de hielo. Al +parecer no fueron tan rápidos o nosotros no fuimos tan lentos. El punto es que esperar una hora alas +cuatro de la mañana en medio de un glaciar a cinco mil quinientos metros nos enfrió mucho a pesar de +nuestras chamarras de pluma de ganso. Cuando finalmente liberaron la ruta y pudimos empezar a subir, +pensamos que el frío era suficiente como para no quitarnos las de pluma. Yo peleaba con el arnés +porque terminó debajo de la mía. + +La nieve está en pésimas condiciones. El primer largo tiene hielo duro cubierto por una capa de +hielo poroso. Los piolets se clavan, pero al momento de jalar se caen bloques grandes de hielo +poroso, como si tuviera una capa de esponja quebradiza. Abajo de eso, el hielo duro rebotaba el +piolet. Solamente estoy logrando clavarlo unos milímetros, y no me da nada de confianza. La pared +tiene 80° de inclinación y, aunque Hernán me está asegurando desde arriba, siento que el hielo +cederá en cualquier momento. + +Así funciona nuestro ascenso: primero sube Nehemio, y poco después Hernán; Andrea y Manu suben +asegurados por Nehemio con cuatro metros de distancia; al final subo yo, asegurado por Hernán. Así, +logramos hacerlo más o menos en paralelo y no perder tanto tiempo subiendo una cordada después de la +otra. + +El grupo de argentinos está en el último largo, ya para llegar a la arista de la cumbre, podemos +verlos desde donde estamos. Hay viento fuerte y vemos cómo suben las nubes y encierran a nuestros +amigos argentinos. No tardó en encerrarnos a nosotros. + +El viento nos llega de lado, y cómo es húmedo, nos llena de escarcha todo el lado izquierdo del +cuerpo. Tampoco podemos ver mucho. La visibilidad ha de ser de unos 30 metros. Ya no escucho a +Hernán gritar que ya puedo empezar a escalar, pero veo cómo Nehemio me hace señas como de _ya puedes +subir_. + +En las reuniones, mientras esperamos a que los guías suban y monten la siguiente, nos congelamos. +Las manos frías y entumidas sirven de poco al momento de querer poner un mosquetón en mi arnés, que +sigue debajo de mi chamarra de pluma. No siento los pies y estoy temblando violentamente. + +--- + +_¿Van a escalar?_, -- Sí -- _¡Órale, qué chévere!_ El lobby del hostal parecía mercado con todas las +niñas que estaban de viaje escolar corriendo y gritando. + +Ya estamos en la van camino al Valle del Ishinka. Chara olvidó el libro de chistes que nos dijo que +iba a traer. Lo que sí trajo fue un balón para jugar fútbol en el campo base. Me canso solo de +imaginarlo, un partido de fútbol a cuatro mil metros, ¿a quién se le ocurre? + +Este valle es mucho más bonito que el de Santa Cruz. El río tiene un color azul turquesa, hay +árboles con corteza roja que hacen una especie de túnel sobre el camino. Hay mucha vegetación. Sobre +todo, hay sombra y viento que nos refresca en la subida. La comida a mitad de camino fue una pieza +de pollo en guisado y un poco de camote; exquisito. + +El campo base está lleno de gente. Dice Manu que es como Disneylandia. Contamos unas 60 tiendas, más +los que estén en el refugio. La vista del Toclla es impresionante, tiene un glaciar imponente. La +idea es subir al día siguiente y encumbrar el 15 + +Recibimos el pronóstico del clima. Mañana estará tranquilo, y el domingo habra viento, nieve, lluvia +y frío. Así que, ¡cambio de planes! Mañana haremos el _summit push_. Nos vamos a saltar el campo +alto e iremos directo a la cima. + +Hernán dice que debemos despertarnos a las diez de la noche. El camino es largo y si queremos llegar +a la cumbre a buena hora debemos salir a las once. Son las siete y apenas estamos terminando de +cenar, con suerte dormiremos dos horas. Lo bueno es que ya tenemos todo listo para mañana: las +mochilas, las botas y la ropa. + +-- !Ring ring, Manuel! -- Grita Nehemio para despertarnos. Nos dio cuarenta minutos más de sueño, +pero ya hay que arreglarnos, comer algo rápido y salir. En realidad seguimos llenos de la cena de +hace rato, y con un té y una galleta con mermelada nos basta. Empezamos la escalada a las 23:45, y +Ezequiel nos está ayudando a cargar algunas cosas hasta el glaciar, mis botas incluidas. + +El camino por la morrena está horrible. Hay zonas con nieve y hielo, pero todavía no podemos +ponernos los crampones, entonces está muy resbaloso. Tres horas y media después, con los pies +helados, llegamos a la lengua del glaciar. Las cardadas son las mismas que en el Arteson; Andrea, +Manuel y Nehemio en una, y Hernán y yo en la otra. + +El viento empezó como a las tres de la mañana. Un viento helado y seco. Lo bueno es que no se siente +tan frío como el viendo en el Artesonraju y mientras sigamos caminando, no está tan mal. + +Creo que llevamos buen paso. Muy a lo lejos se ven cuatro linternas, pero cada vez estamos más cerca +de ellas. Ahora caminamos en zigzag para esquivar todas las grietas que tiene el glaciar, así que +para cuando alcanzamos a los que están delante de nosotros, ya amaneció. + +Los primeros que alcanzamos son un francés con su guía. Nos dijo Nehemio que el guía es el anterior +director técnico de la escuela de guías, o sea el que se encargaba de observar el comportamiento de +los aspirantes durante los tres años que dura el curso. ya decidieron bajarse de la montaña porque +la nieve está en malas condiciones. La otra cordada es una pareja de mexicanos. Están intentando +subir por un serac roto, que bloquea la ruta. Los esperamos solo un rato; sin embargo, parece que la +ruta en realidad va por otro lado. El glaciar en esa zona tiene una grieta que estaba bloqueando el +paso de los escaladores en expediciones pasadas. Nos habían contado que la forma de superarlo era +yendo por la izquierda. + +Dejamos a los otros mexicanos pelearse con el serac y nos movimos a una rampa de nieve más a la +izquierda para evitar pasar por la grieta del serac. Yo estoy asegurando a Hernán, que sube por la +rampa diciendo que la nieve está muy suelta y en malas condiciones. Veo cómo cada movimiento hace +que caiga nieve por montones. Al mismo tiempo, Manu asegura a Nehemio, que sube unos metros debajo +de Hernán. Es la primera vez que veo que protegen la ruta. Hernán puso un tornillo de hielo, que +también usó Nehemio. Para que hagan eso, la nieve debe estar verdaderamente mala. + +Subo pasando a Andrea y Manu por la derecha; ellos siguen anclados en la reunión. Ahora entiendo el +sufrimiento de los guías. Esta nieve está tan suelta que aún con piolets y crampones, no me siento +con la confianza diff --git a/content/blog/2021-02-07-infrastructure-as-code.md b/content/blog/2021-02-07-infrastructure-as-code.md new file mode 100644 index 0000000..056e3a8 --- /dev/null +++ b/content/blog/2021-02-07-infrastructure-as-code.md @@ -0,0 +1,257 @@ ++++ +title = "Infrastructure as Code" +description = "Notes from the book" +date = 2021-02-07 +updated = 2025-03-12 + +[taxonomies] +tags = ["Infrastructure", "Automation", "Quality"] ++++ + +# Introduction + +This is a summary of the book **Infrastructure as Code (2nd edition)** by Kief Morris, where I state the things that stood out the most to +me. + +# What is Infrastructure as Code? + +Organizations are becoming increasingly "digital" [^1], and as they do, the IT infrastructure becomes more and more complex: more services, +more users, more business activities, suppliers, products, customers, stakeholders... and the list goes on and on. + +[^1]: short for 'software systems are essential for our business' + +Infrastructure automation tools help manage this complexity by keeping the entire infrastructure as code. This will help by optimizing for +change. People say: we don’t make changes often enough to justify automating them; we should build first, automate later; we must choose +between speed and quality. These all lead to a "Fragile Mess"; changing the infrastructure becomes a cumbersome error-prone process. + +When prioritizing quality, many organizations put in place complex processes to change even the tiniest detail of their infrastructure; +which eventually are forgotten due to an approaching deadline. Other companies prioritize speed (move fast and break things); infrastructure +that "just works, but no one knows how" is a very dangerous thing to have. Making changes to it becomes more an obscure art than a clear +process. The only sustainable way of maintaining infrastructure is by prioritizing equally both quality and speed; this may seem like an +unattainable ideal, but it is where we find high performers. + +[Dora](https://www.devops-research.com/research.html) identified four key metrics in their _Accelerate_ research: + +1. **Delivery lead time**: The elapsed time it takes to implement, test, and deliver changes to the production system +2. **Deployment frequency**: How often you deploy changes to production systems +3. **Change fail percentage**: What percentage of changes either cause an impaired service or need immediate correction, such as a rollback + or emergency fix +4. **Mean Time to Restore (MTTR)**: How long it takes to restore service when there is an unplanned outage or impairment Organizations that + perform well against + +# Core Practices + +- **Define everything as code**: Enables reusability, consistency and transparency +- **Continuously test and deliver all work in progress**: Build quality in instead of trying to test quality in +- **Build small, simple pieces that you can change independently**: The larger a system is, the harder it is to change, and the easier it is + to break. + +# Principles + +- **Assume systems are unreliable**: Cloud scale infrastructure has so many moving parts, that even when using reliable hardware, systems + fail +- **Make everything reproducible**: It removes the fear and risk of making changes +- **Create disposable things**: It should be possible to add, remove, start, stop, change and move parts of the the system. The cloud + abstracts resources (storage, compute, networking) from physical hardware. The system should be able to dispose faulty parts and heal + itself. +- **Minimize variation**: Keep the minimum number of different _types_ of pieces possible; It’s easier to manage one hundred identical + servers than five completely different servers. +- **Ensure you can repeat any process**: This will help make things reproducible + +Of course, to use code to define and manage infrastructure, a dynamic infrastructure platform is required. The platform should expose its +functionality to provision resources via APIs or something of the sorts, think Amazon Web Services, Microsoft Azure, Google Cloud Platform, +Digital Ocean, even VMWare. + +# Infrastructure code + +If I want to create a server, it is easier, and faster to go to the platform (AWS) and click through the GUI instead of writing a script for +it. On the other hand, provisioning it through code will make the process reusable, consistent and transparent. It is obvious that defining +this server as code, is the better approach. Defining everything as code enables you to leverage speed to improve quality, much like Agile +uses speed to improve software quality by having tight feedback loops and iterating on that feedback. + +Infrastructure code can use declarative or imperative +languages[^2: "Imperative code is a set of instructions that specifies how to make a thing happen. Declarative code specifies what you want, without specifying how to make it happen."]. + +# Infrastructure stacks + +An infrastructure stack is a group of resources that are defined, provisioned and updated as a unit. For example: A stack may include a +virtual machine, a disk volume and a subnet. + +Examples of stack management tools include: + +- [HashiCorp Terraform](https://www.terraform.io/) +- [AWS CloudFormation](https://aws.amazon.com/cloudformation/) +- [Azure Resource Manager](https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/overview) +- [Google Cloud Deployment Manager](https://cloud.google.com/deployment-manager/docs/) +- [OpenStack Heat](https://wiki.openstack.org/wiki/Heat) +- [Pulumi](https://www.pulumi.com/) +- [Bosh](https://bosh.io/docs/) + +## Patterns and Antipatterns for Structuring Stacks + +One challenge with infrastructure design is deciding how to size and structure stacks. + +### Antipatterns + +- **Monolithic stack**: An entire system into one stack. Changing a large stack is riskier than changing a smaller stack. More things can go + wrong. Larger stacks take longer to provision and change. Because of the risk and slowness, people make less changes to it, and with less + frequency. This increases the levels of technical debt + +### Patterns + +- **Application group stack**: Multiple, related pieces of a system into stacks. It's common for application group stacks to grow into + monolithic stacks; but it can work well when a single team owns the infrastructure of all the pieces of an application. +- **Service stack**: Infrastructure for a single application into a single stack. Service stacks align the boundaries of infrastructure to + the software that runs on it. There could be an unnecessary duplication of code, which can encourage inconsistency. Reusing shareable + modules is encouraged. +- **Micro stack**: Breaks the infrastructure for a given application or service into multiple stacks. Different parts of a service’s + infrastructure may change at different rates. Although parts are smaller, having many of them increases complexity. + +# Building Environments with Stacks + +Environments and stacks are both collections of resources. What makes environments different is that they are organized around a particular +purpose (support the testing phase, provide service in a geographical region). An environment is composed by one or multiple stacks. +Although possible, a stack should not provision multiple environments. + +Consistency across environments is one of the main drivers of Infrastructure as Code. If the testing environment is not the same as the one +in production, you may find that some things behave differently; you may even push broken code that "works" in the testing environment. How +many time have we heard "it runs okay in my local environment"? + +## Antipattern: Multi-Environment Stack + +It defines and manages multiple environments in a single stack instance. Every time you change a testing environment, you risk breaking the +production if there was a mistake, a bug in the tools or somewhere in the pipeline. + +## Antipattern: Copy-Paste Environment + +It uses separate source-code projects to manage each environment. Because environments should be identical, people resort to copy-pasting +the code from the modified stack into the other ones. As you can imagine, this can get messy pretty fast. Maybe some tweaks to reduce costs +are incompatible with the production environment. This causes configuration drift, and makes it confusing for people looking at the code for +the first time. + +## Pattern: Reusable Stack + +Here, you maintain a single source-code project, with enough configuration parameters to provision all the required environments. However, +it could be too rigid for situations where environments must be heavily customized. + +The reusable stack should be the foundation for any new environment. There are several ways to configure the configuration parameters it +requires to be unique, let's look at those. + +## Antipattern: Manual Stack Parameters + +The most natural approach to provide values for a stack instance is to type the values on the command line manually. + +```bash +stack up environment=production --source my-stck/src +# FAILURE: No such directory 'my-stck/src' + +stack up environment=production --source my-stack/src +# SUCCESS: new stack 'production' created + +stack destroy environment=production --source my-stack/src +# SUCCESS: stack 'production' destroyed + +stack up environment=production --source my-stack/src +# SUCCESS: existing stack 'production' modified +``` + +## Pattern: Stack Environment Variables + +The stack environment variables pattern involves setting parameter values as environment variables for the stack tool to use. This pattern +is often combined with another pattern to set the environment variables. + +```bash +export STACK_ENVIRONMENT=test +export STACK_CLUSTER_MINIMUM=1 +export STACK_CLUSTER_MAXIMUM=1 +export STACK_SSL_CERT_PASSPHRASE="correct horse battery staple" +``` + +## Pattern: Scripted Parameters + +Scripted parameters involves hardcoding the parameter values into a script that runs the stack tool. You can write a separate script for +each environment or a single script that includes the values for all of your environments + +```ruby +if ${ENV} == "test" + stack up cluster_maximum=1 env="test" +elsif ${ENV} == "staging" + stack up cluster_maximum=3 env="staging" +elsif ${ENV} == "production" + stack up cluster_maximum=5 env="production" +end +``` + +## Pattern: Stack Configuration Files + +We can manage the parameters for each stack instance in separate files. + +For example: + +```text +├── src/ +│ ├── cluster.tf +│ ├── host_servers.tf +│ └── networking.tf +├── environments/ +│ ├── test.tfvars +│ ├── staging.tfvars +│ └── production.tfvars +└── test/ +``` + +This pattern is simple and very useful when the environments don't change often, as it requires to create and commit a new configuration +file per environment. It is also slower to reach the stable production environments, since these files have to progress through the various +stages before being committed to the main branch. + +## Pattern: Wrapper Stack + +Let's say we write our infrastructure code as reusable modules, kind of like a library. A wrapper stack is a code project that imports and +uses those modules, passing the appropriate parameters to them. In that sense, every environment is its own code project. We can set up +independent repositories for them using git. The only thing we must make sure does not get into these repos are secrets, for those we can +leverage other patterns, like using environment variables, or untracked configuration files. + +There's a catch, though. There is added complexity by having to manage the reusable modules and the projects that use them. Furthermore, +having separate projects tempt people into adding specific logic to one of them to customize it without including it upstream. + +## Pattern: Pipeline Stack Parameters + +Delivery pipelines allow us to set at the very least environment variables via some admin panel, a CLI or even an API. We can hook up our +project in one such pipeline, and let it configure our project. An added bonus is that the entire team can use it without having to share +parameters or even secrets. The catch is that we become dependant on the pipeline. If for some reason, the pipeline is offline, or simply +not accessible, we cannot make changes to our infrastructure. + +Also, the first thing attackers look for when gaining access to a network are CI/CD servers because they are full of admin-level +credentials. + +## Pattern: Stack Parameter Registry + +We can store all our parameters in a centralized registry, this can range from simple files in some file server, to a Consul cluster or a +SQL database. Of course, we have to use another pattern to set the connection parameters to our registry; but once plugged it, changing it +becomes a matter of executing a query, or modifying a file. + +The main issue with this pattern is the fact that we are adding something more to manage, the registry itself. This registry is an extra +moving piece and a potential point of failure. + +# Testing + +If good testing yields great results for application development, we can assume it will be very useful while defining our infrastructure as +code. + +Testing infrastructure code is not easy. Many frameworks use declarative languages, which makes unit tests for these declarations somewhat +useless. If we declare that we want a server with 1 CPU and 2 GB of RAM, the test that we write might check for these attributes. We are +simply re-stating what we declared in the code. With this test, we are testing that the provider and the tool works as promised, not that +our code does what we intend. Unit testing is reserved, then, for those sneaky pieces of code that are influenced by configuration +parameters, and why not, for testing around known issues with the tools of platforms we use. + +The heavy burden will lie on integration tests. If we want three subnets: `subnet-a` is public, `subnet-b` is private, `subnet-c` is private +and does not have access to the other subnets; we can test for exactly that, that we can reach `subnet-a` through internet, that `subnet-b` +can access `subnet-a`, and that `subnet-c` cannot access the other subnets. This is much more useful, since we are testing several moving +pieces. Provisioning networks include access rules, routing tables, subnet masks, internet gateways, nat gateways, and some other pieces +that provisioned together will provide a useful network; we might as well test that it works like we want it to. + +Integration tests are slower than unit tests, since we must provision actual resources on real platforms. The slowness gets worse when we +make end-to-end tests. When we provision our entire infrastructure to test, that is. + +The key thing is to identify risks, in our case, our risk is that we may accidentally expose `subnet-c` to the outside world; therefore we +test to ensure that risk does not become a reality. diff --git a/content/blog/2025-02-17-gitea-open-source-github-alternative.md b/content/blog/2025-02-17-gitea-open-source-github-alternative.md new file mode 100644 index 0000000..a3d8537 --- /dev/null +++ b/content/blog/2025-02-17-gitea-open-source-github-alternative.md @@ -0,0 +1,207 @@ ++++ +title = "Gitea - Open Source GitHub Alternative" +description = "Self-hosting Gitea, a lightweight GitHub alternative written in Go." +date = 2025-02-17 +updated = 2025-03-12 + +[taxonomies] +tags = ["Self-Hosting", "CI/CD", "Linux", "Gitea", "Go"] ++++ +## Introduction + +Gitea is a lightweight, self-hosted alternative to GitHub, providing Git repository hosting, CI/CD, package management, and team +collaboration features. If you value privacy, control, and flexibility over your development workflow, self-hosting Gitea can be a great +choice. + +This guide will walk you through setting up Gitea using Docker and Caddy as a reverse proxy, along with configuring a runner for CI/CD +pipelines. + +## Prerequisites + +Before we start, ensure you have the following: + +1. **A Linux server** - I am using [Pop!_OS 24.04 LTS alpha](https://system76.com/cosmic/). +2. **A domain name** pointing to your server - I am using [Cloudflare](https://www.cloudflare.com/). +3. **A container runtime** - I am using [Docker](https://www.docker.com/). +4. **A reverse proxy** - I am using [Caddy](https://caddyserver.com/). + +## Installation + +### Step 1: Create a Storage Location + +To store repository files and application data, I will create a dataset in my ZFS pool at `/data` (ZFS is optional; use any directory if ZFS +is unavailable): + +```bash +sudo mkdir -p /data/gitea +``` + +### Step 2: Create a Gitea User + +We need a dedicated user for running Gitea: + +```bash +adduser \ + --system \ + --shell /bin/bash \ + --gecos 'Gitea' \ + --group \ + --disabled-password \ + --home /home/gitea \ + gitea +``` + +### Step 3: Set Up Docker Compose + +Create a `docker-compose.yaml` file for Gitea: + +```yaml +# ~/gitea/compose.yaml +services: + server: + image: docker.gitea.com/gitea:latest + environment: + USER: gitea + USER_UID: 122 # Ensure this matches the Gitea user ID + USER_GID: 126 # Ensure this matches the Gitea group ID + GITEA__database__DB_TYPE: postgres + GITEA__database__HOST: db:5432 + GITEA__database__NAME: gitea + GITEA__database__USER: gitea + GITEA__database__PASSWD: __REDACTED__ + GITEA__server__DOMAIN: gitea.example.com + GITEA__server__HTTP_PORT: 9473 + GITEA__server__ROOT_URL: https://gitea.example.com/ + GITEA__server__DISABLE_SSH: false + GITEA__server__SSH_PORT: 22022 + restart: always + volumes: + - /data/gitea:/data + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + ports: + - "9473:9473" + - "22022:22" + depends_on: + - db + + db: + image: docker.io/library/postgres:17 + restart: always + environment: + POSTGRES_USER: gitea + POSTGRES_PASSWORD: __REDACTED__ + POSTGRES_DB: gitea + volumes: + - postgres-data:/var/lib/postgresql/data + +volumes: + postgres-data: + driver: local +``` + +### Step 4: Start Gitea + +Run the following command to start Gitea: + +```bash +docker compose up --detach +``` + +> **Note:** Gitea's SSH server is set to port `22022` because port `22` is already in use by my existing SSH setup. Adjust this as needed. + +## Setting Up the Reverse Proxy + +We will use Caddy to handle HTTPS and reverse proxy requests for Gitea. Add the following to your `Caddyfile`: + +```plaintext +gitea.example.com { + tls { + dns cloudflare __CLOUDFLARE_TOKEN__ + resolvers 1.1.1.1 + } + reverse_proxy localhost:9473 +} +``` + +Reload Caddy to apply the changes: + +```bash +sudo systemctl reload caddy +``` + +## Configuration + +Now, open your browser and navigate to `https://gitea.example.com` (or `http://localhost:9473` if testing locally). Complete the setup by +filling in: + +- Admin account details +- Database configuration +- SMTP settings (if needed) + +Once configured, click **Install Gitea**. + +## Setting Up CI/CD + +Gitea has built-in CI/CD capabilities. We will deploy an [Act Runner](https://docs.gitea.com/usage/actions/act-runner) to run pipelines. + +### Step 1: Create Runner Configuration + +The following `runner-config.yaml` addresses an issue where the runner cannot cache due to job containers being on different networks: + +```yaml +# ~/gitea/runner-config.yaml +cache: + enabled: true + dir: "/data/cache" + host: "192.168.1.10" # Replace with your server's private IP + port: 9012 # Port Gitea listens on +``` + +### Step 2: Update Docker Compose + +Modify `docker-compose.yaml` to add the runner service: + +```yaml +# ~/gitea/compose.yaml +services: + runner: + image: docker.io/gitea/act_runner:latest + environment: + CONFIG_FILE: /config.yaml + GITEA_INSTANCE_URL: https://gitea.example.com/ + GITEA_RUNNER_REGISTRATION_TOKEN: __REDACTED__ + GITEA_RUNNER_NAME: runner-1 + ports: + - "9012:9012" + volumes: + - ./runner-config.yaml:/config.yaml + - gitea-runner-data:/data + - /var/run/docker.sock:/var/run/docker.sock + +volumes: + gitea-runner-data: + driver: local +``` + +### Step 3: Start the Runner + +Run the following command: + +```bash +docker compose up --detach +``` + +## Conclusion + +Self-hosting Gitea is relatively straightforward and provides full control over your development workflow. However, setting up CI/CD and +reverse proxying may require some tweaking to fit your setup. + +### Next Steps + +- Configure **OAuth authentication** (e.g., GitHub, GitLab, LDAP). +- Set up **automated backups** to avoid data loss. +- Enable **Gitea Webhooks** for integration with external services. + +If you have any questions, feel free to reach out. Happy coding! + diff --git a/content/blog/2025-03-12-self-hosting-a-blog-in-2025.md b/content/blog/2025-03-12-self-hosting-a-blog-in-2025.md new file mode 100644 index 0000000..c82c337 --- /dev/null +++ b/content/blog/2025-03-12-self-hosting-a-blog-in-2025.md @@ -0,0 +1,189 @@ ++++ +title = "Self-Hosting a Blog in 2025" +description = "A step-by-step guide to hosting your own website using Zola, Caddy, and Gitea." +date = 2025-03-12 +updated = 2025-03-12 + +[taxonomies] +tags = ["Self-Hosting", "CI/CD", "Linux", "Caddy", "Zola"] + +[extra] +social_media_card = "img/social_cards/blog_self_hosting_a_blog_in_2025.jpg" ++++ + +## Introduction + +[Zola](https://www.getzola.org/) is a static site generator written in Rust that is fast, simple, and easy to use. This blog is built using +Zola and hosted on a Linux server with [Caddy](https://caddyserver.com/) as the web server. + +This guide will walk you through setting up Zola and Caddy to self-host your website efficiently. + +## Prerequisites + +Before starting, ensure you have the following: + +1. **A Linux server** – I am using [Pop!_OS 24.04 LTS alpha](https://system76.com/cosmic/). +2. **A domain name** pointing to your server – I use [Cloudflare](https://www.cloudflare.com/). +3. **A reverse proxy** – [Caddy](https://caddyserver.com/) handles this role. +4. **A CI/CD platform** – I use [Gitea](/blog/gitea-open-source-github-alternative) for automated deployments. +5. **A privacy-focused analytics tool** – I use [Plausible](https://plausible.io/). + +## Installation + +### Step 1: Install Zola + +Since there is no precompiled package for Pop!_OS 24.04 LTS alpha, we will install Zola from source: + +```bash +git clone https://github.com/getzola/zola.git +cd zola +cargo install --path . --locked +zola --version +``` + +### Step 2: Create a New Site + +Initialize a new Zola site: + +```bash +zola init blog +cd blog +git init +echo "public" > .gitignore +``` + +### Step 3: Install a Zola Theme + +I use the [tabi](https://github.com/welpo/tabi.git) theme. To install it: + +```bash +git submodule add https://github.com/welpo/tabi.git themes/tabi +``` + +### Step 4: Configure Zola & Tabi + +Zola uses a `config.toml` file for configuration. Below is a sample configuration: + +```toml +base_url = "https://www.aldofunes.com" +title = "Aldo Funes" +description = "Human being in the making" +default_language = "en" +theme = "tabi" +compile_sass = false +minify_html = true +author = "Aldo Funes" +taxonomies = [{ name = "tags" }, { name = "categories" }] +build_search_index = true + +[markdown] +highlight_code = true +highlight_theme = "css" +highlight_themes_css = [{ theme = "dracula", filename = "css/syntax.css" }] +render_emoji = true +external_links_class = "external" +external_links_target_blank = true +smart_punctuation = true + +[search] +index_format = "elasticlunr_json" + +[extra] +stylesheets = ["css/syntax.css"] +remote_repository_url = "https://gitea.funes.me/aldo/blog" +remote_repository_git_platform = "gitea" +mermaid = true +show_previous_next_article_links = true +toc = true +favicon_emoji = "👾" +``` + +### Step 5: Add Content + +Zola uses Markdown for content creation, and its directory structure is intuitive. Use your favorite text editor to start writing articles. + +### Step 6: Deploy Your Site + +To serve the site with Caddy, place the generated files in `/www/blog` and configure Caddy with the following `Caddyfile`: + +```Caddyfile +aldofunes.com, www.aldofunes.com { + tls { + dns cloudflare __CLOUDFLARE_TOKEN__ + resolvers 1.1.1.1 + } + root * /www/blog + file_server + handle_errors { + rewrite * /{err.status_code}.html + file_server + } + header Cache-Control max-age=3600 + header /static/* Cache-Control max-age=31536000 +} +``` + +### Step 7 (Optional): Set Up a CDN + +Using Cloudflare as a CDN improves performance and security. Configure a DNS record and enable Cloudflare proxying to benefit from caching +and DDoS protection. + +### Step 8: Automate Deployment with CI/CD + +To automate deployments with Gitea, create `.gitea/workflows/deploy.yaml`: + +```yaml +name: Deploy +on: + push: + branches: + - main + +jobs: + build-and-test: + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v4 + with: + submodules: true + + - name: Check 🔍 + uses: zolacti/on@check + with: + drafts: true + + - name: Build 🛠 + uses: zolacti/on@build + + - name: Deploy 🚀 + uses: appleboy/scp-action@v0.1.7 + with: + host: ${{ vars.ATLAS_SSH_HOST }} + username: ${{ vars.ATLAS_SSH_USERNAME }} + key: ${{ secrets.ATLAS_SSH_KEY }} + port: ${{ vars.ATLAS_SSH_PORT }} + source: public + target: /www/blog + rm: true + overwrite: true + strip_components: 1 +``` + +Set these environment variables in Gitea Actions: + +- `ATLAS_SSH_HOST` +- `ATLAS_SSH_USERNAME` +- `ATLAS_SSH_PORT` + +And add the secret key: + +- `ATLAS_SSH_KEY` + +These credentials enable secure deployment via SCP. + +## Conclusion + +You now have a fully self-hosted website powered by Zola and Caddy. With automated CI/CD using Gitea, you can focus on writing content while +Gitea handles deployment. Enjoy your self-hosted blog! + diff --git a/content/blog/infrastructure_as_code.md b/content/blog/infrastructure_as_code.md deleted file mode 100644 index 0a69a04..0000000 --- a/content/blog/infrastructure_as_code.md +++ /dev/null @@ -1,13 +0,0 @@ -+++ -title = "Infrastructure as code" -+++ - -```rust - -pub async fn main() -> Result<(), Error> { - let mut infrastructure = Infrastructure::new(); - dbg!(infrastructure); -} -``` - -This is [a link](https://example.com) to example.com. \ No newline at end of file diff --git a/content/pages/about.md b/content/pages/about.md index 38a948f..05fdbfb 100644 --- a/content/pages/about.md +++ b/content/pages/about.md @@ -1,7 +1,10 @@ +++ -title = "About" +title = "About Me" template = "info-page.html" path = "about" +++ -# About me \ No newline at end of file +I am Aldo Funes, a passionate engineer capable of putting together the most complex workflows, and enjoying every bit. + +I am a software engineer with a strong background in computer science and a passion for technology. I have experience in software +development, data analysis, and machine learning. I am always looking for new challenges and opportunities to learn and grow. diff --git a/content/projects/airtm.md b/content/projects/airtm.md index ba273c8..0a325a0 100644 --- a/content/projects/airtm.md +++ b/content/projects/airtm.md @@ -1,12 +1,12 @@ +++ title = "Airtm" -description = "A dollar wallet" +description = "Airtm is the most connected digital wallet in the world, offering an integrated US Virtual Account for non-US citizens, direct withdrawals from partners like Payoneer and access to over 500+ payment methods to convert and withdraw funds to your local currency. " weight = 1 [taxonomies] -tags = ["tag one", "tag 2", "third tag"] +tags = ["typescript", "node.js", "event-driven"] [extra] -local_image = "img/seedling.png" +local_image = "img/airtm-logo.svg" link_to = "https://airtm.com" +++ \ No newline at end of file diff --git a/content/projects/lovis-api.md b/content/projects/lovis-api.md new file mode 100644 index 0000000..70877bf --- /dev/null +++ b/content/projects/lovis-api.md @@ -0,0 +1,6 @@ ++++ +title = "LOVIS API" +url = "https://www.lovis.com" +weight = 1 +description = "The API allows customers to integrate their systems with LOVIS EOS. The interface area consists of a GraphQL API and webhook notifications." ++++ \ No newline at end of file diff --git a/content/projects/lovls-ecommerce.md b/content/projects/lovls-ecommerce.md new file mode 100644 index 0000000..c148a52 --- /dev/null +++ b/content/projects/lovls-ecommerce.md @@ -0,0 +1,14 @@ ++++ +title = "E-Commerce integration with LOVIS EOS" +weight = 1 +description = "Integration of LOVIS EOS with E-Commerce plaforms to automate inventory, logistics and invoicing." + +[extra] +social_media_card = "img/social_cards/projects_lovls_ecommerce.jpg" ++++ + +Integration of LOVIS EOS with E-Commerce plaforms to automate inventory, logistics and invoicing. + +- Shopify: [beerhouse.mx](https://www.beerhouse.mx/) +- Mercado Libre: [La Liga de la Cerveza](https://www.mercadolibre.com.mx/perfil/LALIGADELACERVEZASDERL) +- Amazon Marketplace diff --git a/content/projects/qswinwear.md b/content/projects/qswinwear.md new file mode 100644 index 0000000..aa0c19c --- /dev/null +++ b/content/projects/qswinwear.md @@ -0,0 +1,11 @@ ++++ +title = "End-to-end custom swimwear production system" +weight = 1 +description = "Customers are able to design and purchase customized swimwear products. When an order is completed, the factory receives the information required to produce them. It is a popular product for teams and schools." + +[taxonomies] +tags = ["react", "graphql", "nodejs"] + +[extra] +link_to = "https://designer.qswimwear.com" ++++ \ No newline at end of file diff --git a/content/projects/tabi.md b/content/projects/tabi.md deleted file mode 100644 index 96d3a55..0000000 --- a/content/projects/tabi.md +++ /dev/null @@ -1,11 +0,0 @@ -+++ -title = "tabi" -description = "A feature-rich modern Zola theme with first-class multi-language support." -weight = 1 - -[taxonomies] -tags = ["tag one", "tag 2", "third tag"] - -[extra] -local_image = "img/seedling.png" -+++ \ No newline at end of file diff --git a/public/code/social-cards-zola b/public/code/social-cards-zola index bd92625..21ff2f3 100755 --- a/public/code/social-cards-zola +++ b/public/code/social-cards-zola @@ -56,6 +56,8 @@ function convert_filename_to_url() { url="${url%%_index*}" fi + url=$(echo "$url" | sed -r 's/([0-9]{4}-[0-9]{2}-[0-9]{2}-)//') # Replace datetime. + # Return the final URL with a single trailing slash. full_url="${lang_code}${url}" echo "${full_url%/}/" diff --git a/public/search_index.en.js b/public/search_index.en.js deleted file mode 100644 index 9af6a11..0000000 --- a/public/search_index.en.js +++ /dev/null @@ -1 +0,0 @@ -window.searchIndex = {"fields":["title","body"],"pipeline":["trimmer","stopWordFilter","stemmer"],"ref":"id","version":"0.9.5","index":{"body":{"root":{"docs":{},"df":0,"a":{"docs":{},"df":0,"i":{"docs":{},"df":0,"r":{"docs":{},"df":0,"t":{"docs":{},"df":0,"m":{"docs":{"http://127.0.0.1:1111/projects/airtm/":{"tf":1.0}},"df":1}}}},"r":{"docs":{},"df":0,"c":{"docs":{},"df":0,"h":{"docs":{},"df":0,"i":{"docs":{},"df":0,"v":{"docs":{"http://127.0.0.1:1111/archive/":{"tf":1.0}},"df":1}}}}}},"b":{"docs":{},"df":0,"l":{"docs":{},"df":0,"o":{"docs":{},"df":0,"g":{"docs":{"http://127.0.0.1:1111/":{"tf":1.0},"http://127.0.0.1:1111/blog/":{"tf":1.0}},"df":2}}}},"c":{"docs":{},"df":0,"o":{"docs":{},"df":0,"d":{"docs":{},"df":0,"e":{"docs":{"http://127.0.0.1:1111/blog/infrastructure-as-code/":{"tf":1.0}},"df":1}}}},"e":{"docs":{},"df":0,"x":{"docs":{},"df":0,"a":{"docs":{},"df":0,"m":{"docs":{},"df":0,"p":{"docs":{},"df":0,"l":{"docs":{},"df":0,"e":{"docs":{},"df":0,".":{"docs":{},"df":0,"c":{"docs":{},"df":0,"o":{"docs":{},"df":0,"m":{"docs":{"http://127.0.0.1:1111/blog/infrastructure-as-code/":{"tf":1.0}},"df":1}}}}}}}}}}},"h":{"docs":{},"df":0,"o":{"docs":{},"df":0,"m":{"docs":{},"df":0,"e":{"docs":{"http://127.0.0.1:1111/":{"tf":1.0}},"df":1}}}},"i":{"docs":{},"df":0,"n":{"docs":{},"df":0,"f":{"docs":{},"df":0,"r":{"docs":{},"df":0,"a":{"docs":{},"df":0,"s":{"docs":{},"df":0,"t":{"docs":{},"df":0,"r":{"docs":{},"df":0,"u":{"docs":{},"df":0,"c":{"docs":{},"df":0,"t":{"docs":{},"df":0,"u":{"docs":{},"df":0,"r":{"docs":{"http://127.0.0.1:1111/blog/infrastructure-as-code/":{"tf":1.0}},"df":1}}}}}}}}}}}}},"l":{"docs":{},"df":0,"i":{"docs":{},"df":0,"n":{"docs":{},"df":0,"k":{"docs":{"http://127.0.0.1:1111/blog/infrastructure-as-code/":{"tf":1.0}},"df":1}}}},"n":{"docs":{},"df":0,"e":{"docs":{},"df":0,"w":{"docs":{"http://127.0.0.1:1111/":{"tf":1.0}},"df":1}}},"p":{"docs":{},"df":0,"r":{"docs":{},"df":0,"o":{"docs":{},"df":0,"j":{"docs":{},"df":0,"e":{"docs":{},"df":0,"c":{"docs":{},"df":0,"t":{"docs":{"http://127.0.0.1:1111/projects/":{"tf":1.0}},"df":1}}}}}}},"t":{"docs":{},"df":0,"a":{"docs":{},"df":0,"b":{"docs":{},"df":0,"i":{"docs":{"http://127.0.0.1:1111/projects/tabi/":{"tf":1.0}},"df":1}}}},"w":{"docs":{},"df":0,"e":{"docs":{},"df":0,"l":{"docs":{},"df":0,"c":{"docs":{},"df":0,"o":{"docs":{},"df":0,"m":{"docs":{"http://127.0.0.1:1111/":{"tf":1.0}},"df":1}}}}}}}},"title":{"root":{"docs":{},"df":0,"a":{"docs":{},"df":0,"i":{"docs":{},"df":0,"r":{"docs":{},"df":0,"t":{"docs":{},"df":0,"m":{"docs":{"http://127.0.0.1:1111/projects/airtm/":{"tf":1.0}},"df":1}}}},"r":{"docs":{},"df":0,"c":{"docs":{},"df":0,"h":{"docs":{},"df":0,"i":{"docs":{},"df":0,"v":{"docs":{"http://127.0.0.1:1111/archive/":{"tf":1.0}},"df":1}}}}}},"b":{"docs":{},"df":0,"l":{"docs":{},"df":0,"o":{"docs":{},"df":0,"g":{"docs":{"http://127.0.0.1:1111/blog/":{"tf":1.0}},"df":1}}}},"c":{"docs":{},"df":0,"o":{"docs":{},"df":0,"d":{"docs":{},"df":0,"e":{"docs":{"http://127.0.0.1:1111/blog/infrastructure-as-code/":{"tf":1.0}},"df":1}}}},"h":{"docs":{},"df":0,"o":{"docs":{},"df":0,"m":{"docs":{},"df":0,"e":{"docs":{"http://127.0.0.1:1111/":{"tf":1.0}},"df":1}}}},"i":{"docs":{},"df":0,"n":{"docs":{},"df":0,"f":{"docs":{},"df":0,"r":{"docs":{},"df":0,"a":{"docs":{},"df":0,"s":{"docs":{},"df":0,"t":{"docs":{},"df":0,"r":{"docs":{},"df":0,"u":{"docs":{},"df":0,"c":{"docs":{},"df":0,"t":{"docs":{},"df":0,"u":{"docs":{},"df":0,"r":{"docs":{"http://127.0.0.1:1111/blog/infrastructure-as-code/":{"tf":1.0}},"df":1}}}}}}}}}}}}},"p":{"docs":{},"df":0,"r":{"docs":{},"df":0,"o":{"docs":{},"df":0,"j":{"docs":{},"df":0,"e":{"docs":{},"df":0,"c":{"docs":{},"df":0,"t":{"docs":{"http://127.0.0.1:1111/projects/":{"tf":1.0}},"df":1}}}}}}},"t":{"docs":{},"df":0,"a":{"docs":{},"df":0,"b":{"docs":{},"df":0,"i":{"docs":{"http://127.0.0.1:1111/projects/tabi/":{"tf":1.0}},"df":1}}}}}}},"documentStore":{"save":true,"docs":{"http://127.0.0.1:1111/":{"body":"Welcome to your new blog!\n","id":"http://127.0.0.1:1111/","title":"Home"},"http://127.0.0.1:1111/about/":{"body":"\nAbout me\n","id":"http://127.0.0.1:1111/about/","title":"About"},"http://127.0.0.1:1111/archive/":{"body":"","id":"http://127.0.0.1:1111/archive/","title":"Archive"},"http://127.0.0.1:1111/blog/":{"body":"","id":"http://127.0.0.1:1111/blog/","title":"Blog"},"http://127.0.0.1:1111/blog/infrastructure-as-code/":{"body":"\nThis is a link to example.com.\n","id":"http://127.0.0.1:1111/blog/infrastructure-as-code/","title":"Infrastructure as code"},"http://127.0.0.1:1111/pages/":{"body":"","id":"http://127.0.0.1:1111/pages/","title":""},"http://127.0.0.1:1111/projects/":{"body":"","id":"http://127.0.0.1:1111/projects/","title":"Projects"},"http://127.0.0.1:1111/projects/airtm/":{"body":"","id":"http://127.0.0.1:1111/projects/airtm/","title":"Airtm"},"http://127.0.0.1:1111/projects/tabi/":{"body":"","id":"http://127.0.0.1:1111/projects/tabi/","title":"tabi"}},"docInfo":{"http://127.0.0.1:1111/":{"body":3,"title":1},"http://127.0.0.1:1111/about/":{"body":0,"title":0},"http://127.0.0.1:1111/archive/":{"body":0,"title":1},"http://127.0.0.1:1111/blog/":{"body":0,"title":1},"http://127.0.0.1:1111/blog/infrastructure-as-code/":{"body":2,"title":2},"http://127.0.0.1:1111/pages/":{"body":0,"title":0},"http://127.0.0.1:1111/projects/":{"body":0,"title":1},"http://127.0.0.1:1111/projects/airtm/":{"body":0,"title":1},"http://127.0.0.1:1111/projects/tabi/":{"body":0,"title":1}},"length":9},"lang":"English"} \ No newline at end of file diff --git a/static/code/social-cards-zola b/static/code/social-cards-zola index bd92625..21ff2f3 100755 --- a/static/code/social-cards-zola +++ b/static/code/social-cards-zola @@ -56,6 +56,8 @@ function convert_filename_to_url() { url="${url%%_index*}" fi + url=$(echo "$url" | sed -r 's/([0-9]{4}-[0-9]{2}-[0-9]{2}-)//') # Replace datetime. + # Return the final URL with a single trailing slash. full_url="${lang_code}${url}" echo "${full_url%/}/" diff --git a/static/img/airtm-logo.svg b/static/img/airtm-logo.svg new file mode 100644 index 0000000..b38bae8 --- /dev/null +++ b/static/img/airtm-logo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/static/img/profile.jpg b/static/img/profile.jpg new file mode 100644 index 0000000..80b3221 Binary files /dev/null and b/static/img/profile.jpg differ diff --git a/static/img/social_cards/blog_infrastructure_as_code.jpg b/static/img/social_cards/blog_infrastructure_as_code.jpg new file mode 100644 index 0000000..fe75bed Binary files /dev/null and b/static/img/social_cards/blog_infrastructure_as_code.jpg differ diff --git a/static/img/social_cards/blog_self_hosting_a_blog_in_2025.jpg b/static/img/social_cards/blog_self_hosting_a_blog_in_2025.jpg new file mode 100644 index 0000000..f09a5ed Binary files /dev/null and b/static/img/social_cards/blog_self_hosting_a_blog_in_2025.jpg differ diff --git a/static/img/social_cards/blog_why_i_quit_social_networks.jpg b/static/img/social_cards/blog_why_i_quit_social_networks.jpg new file mode 100644 index 0000000..07a67e8 Binary files /dev/null and b/static/img/social_cards/blog_why_i_quit_social_networks.jpg differ diff --git a/static/img/social_cards/projects_lovls_ecommerce.jpg b/static/img/social_cards/projects_lovls_ecommerce.jpg new file mode 100644 index 0000000..8636c6e Binary files /dev/null and b/static/img/social_cards/projects_lovls_ecommerce.jpg differ