Compare commits

...

75 Commits

Author SHA1 Message Date
3ebb70a524 cande laptop
All checks were successful
continuous-integration/drone/push Build is passing
2024-11-20 12:32:55 +01:00
c4c5cd40af test2
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2024-11-13 15:59:42 +01:00
d0120c4144 test
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2024-11-13 15:05:39 +01:00
32a75322e5 sum pc 2024-11-13 10:41:50 +01:00
7eb0356860 git restore mtime
All checks were successful
continuous-integration/drone/push Build is passing
2024-08-06 19:32:15 +02:00
c50c3ca37a debug
All checks were successful
continuous-integration/drone/push Build is passing
2024-08-06 19:01:28 +02:00
444a937d0b no css
All checks were successful
continuous-integration/drone/push Build is passing
2024-08-06 18:51:24 +02:00
81b8deb4cf build dst
All checks were successful
continuous-integration/drone/push Build is passing
2024-08-06 18:45:02 +02:00
4d13fc9604 :)
All checks were successful
continuous-integration/drone/push Build is passing
2024-08-06 18:29:55 +02:00
000e49afd7 laptops 2024-06-20 20:52:54 +02:00
3bec601f31 yes
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-14 11:49:29 +02:00
9e0f049a52 ubuntu
Some checks failed
continuous-integration/drone/push Build is failing
2024-04-14 11:45:36 +02:00
dbcdea640a standalone
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-14 00:43:47 +02:00
015e7b6ed2 rm
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-14 00:34:39 +02:00
c0debe8c47 _site
Some checks failed
continuous-integration/drone/push Build is failing
2024-04-14 00:32:14 +02:00
c902b1e87f alpine has a differente date command
Some checks failed
continuous-integration/drone/push Build is failing
2024-04-14 00:25:02 +02:00
dacdd27a73 bash
Some checks failed
continuous-integration/drone/push Build is failing
2024-04-14 00:04:28 +02:00
836e85c086 idk
Some checks failed
continuous-integration/drone/push Build is failing
2024-04-14 00:02:12 +02:00
d37c580263 no perms?
Some checks failed
continuous-integration/drone/push Build is failing
2024-04-13 23:02:28 +02:00
e15d9075e7 aaa
Some checks failed
continuous-integration/drone/push Build is failing
2024-04-13 22:29:49 +02:00
9641c44657 code location
Some checks failed
continuous-integration/drone/push Build is failing
2024-04-13 22:27:09 +02:00
7ccd4a8dc6 build location
Some checks failed
continuous-integration/drone/push Build is failing
2024-04-13 22:11:03 +02:00
44c32bf138 new build system and structure using pandoc
Some checks failed
continuous-integration/drone/push Build is failing
2024-04-13 22:06:56 +02:00
9d6ceb3121 _site dir
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-14 15:01:56 +02:00
cdfb6edc04 direct deploy
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-14 14:57:37 +02:00
4eff6d90da back to drone
All checks were successful
continuous-integration/drone/push Build is passing
2023-08-14 14:20:47 +02:00
e89718fc2a label
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 25s
2023-08-14 12:23:18 +02:00
8b218d1497 testing actions
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1m4s
2023-08-14 12:17:57 +02:00
105ac552b0 no m.2
Some checks reported errors
continuous-integration/drone/push Build was killed
2023-06-08 18:40:38 +02:00
7318e2c550 Add 'alba.txt'
Some checks reported errors
continuous-integration/drone/push Build was killed
2023-06-08 16:23:38 +00:00
2e3b4c02f7 Update 'nas.txt' 2023-06-08 16:17:39 +00:00
980b2329ae Update 'tara.txt' 2023-06-08 16:14:25 +00:00
d0633a734d pc 2
All checks were successful
continuous-integration/drone/push Build is passing
2023-01-31 18:32:06 +01:00
5dc359995f vpn 2023-01-31 18:19:20 +01:00
4726f07ea7 unraid 2023-01-31 18:19:20 +01:00
660b758927 10gb lan 2023-01-31 18:19:20 +01:00
5f6a5c00a1 ubuntu user
All checks were successful
continuous-integration/drone/push Build is passing
2022-10-23 00:37:42 +02:00
aa1be6b350 migrate docker and wsl
All checks were successful
continuous-integration/drone/push Build is passing
2022-10-23 00:35:10 +02:00
f90391307d nas.txt
All checks were successful
continuous-integration/drone/push Build is passing
2022-10-20 09:46:07 +02:00
ca1e227cee updated LAN 2022-10-20 09:45:55 +02:00
c405200a7f docker alpine intel quicksync
All checks were successful
continuous-integration/drone Build is passing
2022-10-18 17:50:40 +02:00
d39ebe1367 image include
All checks were successful
continuous-integration/drone/push Build is passing
2022-08-23 17:10:26 +02:00
8c5f777c32 lvm post
All checks were successful
continuous-integration/drone/push Build is passing
2022-07-29 02:09:17 +02:00
e29d86cfd3 links
All checks were successful
continuous-integration/drone/push Build is passing
2022-07-14 22:14:13 +02:00
adc71a66dc xrg.io to sergio.am default files
All checks were successful
continuous-integration/drone/push Build is passing
2022-07-11 14:08:55 +02:00
27cc123416 footer
All checks were successful
continuous-integration/drone/push Build is passing
2022-06-28 12:22:38 +02:00
6547f68742 luci screenshots
All checks were successful
continuous-integration/drone/push Build is passing
2022-06-28 12:17:07 +02:00
c793e85e58 tags
All checks were successful
continuous-integration/drone/push Build is passing
2022-06-27 21:56:40 +02:00
7d32cef0f6 ports and dyndns ipv6
All checks were successful
continuous-integration/drone/push Build is passing
2022-06-27 16:52:26 +02:00
667c6c4e4e post backups
All checks were successful
continuous-integration/drone/push Build is passing
2022-06-24 09:53:57 +02:00
82c3b0e34f openwrt-exclude-clients-dhcp-options
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-20 22:13:42 +02:00
c5f71ebcab typo
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-20 19:37:37 +02:00
9957df4a1a digi
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-18 19:53:28 +02:00
40b194ca33 digi
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-18 19:50:12 +02:00
e4d822b13f ipv6
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-18 19:03:06 +02:00
27a7a8d9a2 clean up
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-18 18:51:58 +02:00
78f5e31764 clean up
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-17 07:31:31 +02:00
253b2fb05a vhost
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-16 16:28:14 +02:00
5c7f2d6f68 cp -r
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-16 15:27:37 +02:00
3bbbea963b rm
Some checks failed
continuous-integration/drone/push Build is failing
2022-05-16 13:51:22 +02:00
5580c7ffd6 permalinks
Some checks failed
continuous-integration/drone/push Build is failing
2022-05-16 13:41:06 +02:00
bc082821f2 rm + cp
Some checks failed
continuous-integration/drone/push Build is failing
2022-05-16 12:12:45 +02:00
85c1815e89 readme
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-16 11:56:48 +02:00
2720c36997 mv
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-16 11:54:37 +02:00
dd0a64dc41 deploy
Some checks failed
continuous-integration/drone/push Build is failing
2022-05-16 11:44:36 +02:00
8fb8c5089b cache simple
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-16 11:17:03 +02:00
c5a7c99524 cache simple
Some checks failed
continuous-integration/drone/push Build is failing
2022-05-15 18:25:34 +02:00
d667c9dd94 env
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-15 15:15:37 +02:00
0d54935839 env
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-15 15:08:08 +02:00
d7eb18249d env
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-15 14:57:07 +02:00
c0971f1cf8 cache settings 2022-05-15 14:55:04 +02:00
5ae9076ccc drone cache
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-15 14:49:22 +02:00
1040f8d94d drone cache
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-15 14:43:59 +02:00
91fa692611 ci location and main only
All checks were successful
continuous-integration/drone/push Build is passing
2022-05-11 20:04:54 +02:00
05b2524f60 basic ci
Some checks reported errors
continuous-integration/drone/push Build was killed
2022-05-11 20:02:32 +02:00
102 changed files with 1398 additions and 2831 deletions

View File

@ -1,8 +1,20 @@
---
kind: pipeline
type: docker
name: deploy-sergio.am
pipeline: steps:
build: - name: build
image: ruby:alpine image: ubuntu:latest
commands: commands:
- bundle install - apt -y update && apt -y install bash git git-restore-mtime jq pandoc
- bundle exec jekyll build - git restore-mtime
branches: main - bash ./build.sh
- rm -rf /var/www/_site && mv _site /var/www/
when:
branch:
- main
node:
location: nexus

8
404.md
View File

@ -1,5 +1,7 @@
--- ---
title: 404 title: 404 Not found!
layout: 404
permalink: "/404.html"
--- ---
Lo que sea que buscabas ya no está aquí.
The content you are looking for it's no longer here.

35
Gemfile
View File

@ -1,35 +0,0 @@
source "https://rubygems.org"
gem "webrick"
# Hello! This is where you manage which Jekyll version is used to run.
# When you want to use a different version, change it below, save the
# file and run `bundle install`. Run Jekyll with `bundle exec`, like so:
#
# bundle exec jekyll serve
#
# This will help ensure the proper Jekyll version is running.
# Happy Jekylling!
gem "jekyll", "~> 4.1.0"
# This is the default theme for new Jekyll sites. You may change this to anything you like.
# If you want to use GitHub Pages, remove the "gem "jekyll"" above and
# uncomment the line below. To upgrade, run `bundle update github-pages`.
# gem "github-pages", group: :jekyll_plugins
# If you have any plugins, put them here!
group :jekyll_plugins do
gem 'jekyll-feed', '~> 0.13'
gem 'jekyll-sitemap', '~> 1.4'
gem 'jekyll-compose', '~> 0.12.0'
gem 'jekyll-postfiles', '~> 3.1'
end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
# gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby]
# Performance-booster for watching directories on Windows
# gem "wdm", "~> 0.1.0" if Gem.win_platform?
#

View File

@ -1,12 +1,10 @@
--- ---
title: Licencia title: Licencia
permalink: /LICENSE/
layout: page
--- ---
MIT License MIT License
Copyright (c) 2021 Sergio Álvarez Copyright (c) 2024 Sergio Álvarez
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -1,16 +1,12 @@
# sergio.am # sergio.am
Notas y apuntes, futuro contenido de static pages en sergio.am Static content.
## Dev
```shell ```shell
docker run --rm --volume="$PWD:/srv/jekyll" --volume="$PWD/vendor/bundle:/usr/local/bundle" -it jekyll/jekyll:3.8 jekyll build python3 -m http.server -b "::" -d dist/ 4000
docker run --rm --volume="$PWD:/srv/jekyll" --volume="$PWD/vendor/bundle:/usr/local/bundle" -p 4000:4000 -it jekyll/jekyll:3.8 jekyll serve --watch
``` ```
```shell http://[::]:4000 (ipv6 to bypass WSL2 networking issues)
cd _site
python3 -m http.server 4000
```
http://localhost:4000

View File

@ -1,88 +0,0 @@
# Site settings
title: sergio.am
description: >- # site description
Notas y apuntes de cosas con las que suelo perder el tiempo, para mi yo del futuro.
lang: es-ES
timezone: Europe/Madrid
image: assets/img/sergio.png # This image used for Open Graph more info https://ogp.me/
mode: dark # default theme "dark" | "light"
# Profile settings
author:
name: Sergio Álvarez
bio: >- # tell to the world
Notas y apuntes para mi yo del futuro.
username: sergioam # general username
github: xergio # github username
twitter: xergio # twitter username
email: correo@sergio.am # email adress
avatar: /assets/img/sergio100w.jpg # change with your own avatar
# URL settings
url: "https://sergio.am" #
baseurl:
permalink: /-/:title/
google_analytics: # leave it blank if not wish
fb_appid:
# Collection setting
collections:
posts:
output: true
# Markdown settings
markdown: kramdown
highlighter: rouge
kramdown:
syntax_highlighter: rouge
# Default front matter
defaults:
- scope:
path: ""
values:
layout: post
comments: false
# Jekyll Compose default front matter
jekyll_compose:
post_default_front_matter:
modified:
tags: []
description:
draft_default_front_matter:
modified:
tags: []
description:
# Homepage limit posts
number_of_posts: 5
# Build settings
# theme: klise
sass:
style: compressed
include:
- _redirects
- .htaccess
exclude:
- CNAME
- Gemfile
- Gemfile.lock
- CHANGELOG.md
- README.md
- node_modules
- CODE_OF_CONDUCT.md
- CONTRIBUTING.md
- lighthouse.png
- klise-*.gem
- klise.gemspec
- vendor
# Plugins
plugins:
- jekyll-feed
- jekyll-sitemap
- jekyll-postfiles

View File

@ -1,8 +0,0 @@
- title: Inicio
url: /
- title: Sobre este sitio
url: /about/
- title: Contacto
url: /contact/

View File

@ -1,105 +0,0 @@
{% capture headingsWorkspace %}
{% comment %}
Version 1.0.4
https://github.com/allejo/jekyll-anchor-headings
"Be the pull request you wish to see in the world." ~Ben Balter
Usage:
{% include anchor_headings.html html=content %}
Parameters:
* html (string) - the HTML of compiled markdown generated by kramdown in Jekyll
Optional Parameters:
* beforeHeading (bool) : false - Set to true if the anchor should be placed _before_ the heading's content
* anchorAttrs (string) : '' - Any custom HTML attributes that will be added to the `<a>` tag; you may NOT use `href`, `class` or `title`
* anchorBody (string) : '' - The content that will be placed inside the anchor; the `%heading%` placeholder is available
* anchorClass (string) : '' - The class(es) that will be used for each anchor. Separate multiple classes with a space
* anchorTitle (string) : '' - The `title` attribute that will be used for anchors
* h_min (int) : 1 - The minimum header level to build an anchor for; any header lower than this value will be ignored
* h_max (int) : 6 - The maximum header level to build an anchor for; any header greater than this value will be ignored
* bodyPrefix (string) : '' - Anything that should be inserted inside of the heading tag _before_ its anchor and content
* bodySuffix (string) : '' - Anything that should be inserted inside of the heading tag _after_ its anchor and content
Output:
The original HTML with the addition of anchors inside of all of the h1-h6 headings.
{% endcomment %}
{% assign minHeader = include.h_min | default: 1 %}
{% assign maxHeader = include.h_max | default: 6 %}
{% assign beforeHeading = include.beforeHeading %}
{% assign nodes = include.html | split: '<h' %}
{% capture edited_headings %}{% endcapture %}
{% for _node in nodes %}
{% capture node %}{{ _node | strip }}{% endcapture %}
{% if node == "" %}
{% continue %}
{% endif %}
{% assign nextChar = node | replace: '"', '' | strip | slice: 0, 1 %}
{% assign headerLevel = nextChar | times: 1 %}
<!-- If the level is cast to 0, it means it's not a h1-h6 tag, so let's try to fix it -->
{% if headerLevel == 0 %}
{% if nextChar != '<' and nextChar != '' %}
{% capture node %}<h{{ node }}{% endcapture %}
{% endif %}
{% capture edited_headings %}{{ edited_headings }}{{ node }}{% endcapture %}
{% continue %}
{% endif %}
{% assign _workspace = node | split: '</h' %}
{% assign _idWorkspace = _workspace[0] | split: 'id="' %}
{% assign _idWorkspace = _idWorkspace[1] | split: '"' %}
{% assign html_id = _idWorkspace[0] %}
{% capture _hAttrToStrip %}{{ _workspace[0] | split: '>' | first }}>{% endcapture %}
{% assign header = _workspace[0] | replace: _hAttrToStrip, '' %}
<!-- Build the anchor to inject for our heading -->
{% capture anchor %}{% endcapture %}
{% if html_id and headerLevel >= minHeader and headerLevel <= maxHeader %}
{% capture anchor %}href="#{{ html_id }}"{% endcapture %}
{% if include.anchorClass %}
{% capture anchor %}{{ anchor }} class="{{ include.anchorClass }}"{% endcapture %}
{% endif %}
{% if include.anchorTitle %}
{% capture anchor %}{{ anchor }} title="{{ include.anchorTitle | replace: '%heading%', header }}"{% endcapture %}
{% endif %}
{% if include.anchorAttrs %}
{% capture anchor %}{{ anchor }} {{ include.anchorAttrs }}{% endcapture %}
{% endif %}
{% capture anchor %}<a {{ anchor }}>{{ include.anchorBody | replace: '%heading%', header | default: '' }}</a>{% endcapture %}
<!-- In order to prevent adding extra space after a heading, we'll let the 'anchor' value contain it -->
{% if beforeHeading %}
{% capture anchor %}{{ anchor }} {% endcapture %}
{% else %}
{% capture anchor %} {{ anchor }}{% endcapture %}
{% endif %}
{% endif %}
{% capture new_heading %}
<h{{ _hAttrToStrip }}
{{ include.bodyPrefix }}
{% if beforeHeading %}
{{ anchor }}{{ header }}
{% else %}
{{ header }}{{ anchor }}
{% endif %}
{{ include.bodySuffix }}
</h{{ _workspace | last }}
{% endcapture %}
{% capture edited_headings %}{{ edited_headings }}{{ new_heading }}{% endcapture %}
{% endfor %}
{% endcapture %}{% assign headingsWorkspace = '' %}{{ edited_headings | strip }}

View File

@ -1,9 +0,0 @@
<div class="author">
<img
class="author-avatar"
src="{{ site.author.avatar }}"
alt="{{ site.author.username }}"
/>
<h2 class="author-name">{{ site.author.name }}</h2>
<p class="author-bio">{{ site.author.bio }}</p>
</div>

View File

@ -1,10 +0,0 @@
<!-- unnecessary file, however you can still use for comment section, e.g disqus -->
<script
src="https://utteranc.es/client.js"
repo="username/reponame"
issue-term="pathname"
label="✨ comment ✨"
theme="github-light"
crossorigin="anonymous"
async
></script>

View File

@ -1,22 +0,0 @@
<footer class="footer">
<span class="footer_item"><a href="/LICENSE">&copy; {{ site.time | date: "%Y" }} Sergio Álvarez</a><a href="https://sergio.am/code/sergio.am">Código fuente y versionado</a></span>
</footer>
<script src="/assets/js/main.js" defer="defer"></script>
{%- if page.google_analytics -%}
<script src="/assets/js/galite.js"></script>
<script>
var galite = galite || {};
galite.UA = "{{ site.google_analytics }}";
</script>
{%- endif -%}
{%- if page.url == '/archive/' -%}
<script src="/assets/js/search.min.js"></script>
<script>
var sjs = SimpleJekyllSearch({
searchInput: document.getElementById('search-input'),
resultsContainer: document.getElementById('search-results'),
json: '/assets/search.json',
});
</script>
{%- endif -%}

View File

@ -1,142 +0,0 @@
<head prefix="og: http://ogp.me/ns#">
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="application-name" content="{{ site.title }}" />
<meta name="apple-mobile-web-app-status-bar-style" content="#fff" />
<meta name="apple-mobile-web-app-title" content="{{ site.title }}" />
<title>
{% if page.title %}{{ page.title | escape }} - {{ site.title }}{% else %}{{
site.title | escape }}{% endif %}
</title>
<link
rel="alternate"
href="{{
page.url | remove: 'index.html' | remove: '.html' | absolute_url
}}"
hreflang="{{ site.lang }}"
/>
<link
rel="canonical"
href="{{
page.url | remove: 'index.html' | remove: '.html' | absolute_url
}}"
/>
{% if paginator.previous_page %}
<link
rel="prev"
href="{{
paginator.previous_page_path
| remove: 'index.html'
| remove: '.html'
}}"
/>
{% endif %} {% if paginator.next_page %}
<link
rel="next"
href="{{
paginator.next_page_path
| remove: 'index.html'
| remove: '.html'
}}"
/>
{% endif %}
<meta
name="description"
content="{{
page.description
| default: site.description
| strip_html
| normalize_whitespace
| truncate: 200
| escape
}}"
/>
<meta name="referrer" content="no-referrer-when-downgrade" />
<meta property="fb:app_id" content="{{ site.fb_appid }}" />
<meta
property="og:site_name"
content="{% if page.title %}{{ page.title | escape }} | {{
site.author.name
}}{% else %}{{ site.title | escape }}{% endif %}"
/>
<meta
property="og:title"
content="{% if page.title %}{{ page.title | escape }} | {{
site.author.name
}}{% else %}{{ site.title | escape }}{% endif %}"
/>
{% if page.location %}
<meta property="og:type" content="article" />
<meta
property="article:publisher"
content="https://web.facebook.com/{{ site.author.facebook }}"
/>
{% else %}
<meta property="og:type" content="website" />
{% endif %}
<meta
property="og:url"
content="{{
page.url | remove: 'index.html' | remove: '.html' | absolute_url
}}"
/>
<meta
property="og:description"
content="{{
page.description
| default: site.description
| strip_html
| normalize_whitespace
| truncate: 200
| escape
}}"
/>
{% if page.image %}
<meta property="og:image" content="{{ page.image | absolute_url }}" />
{% else %}
<meta property="og:image" content="{{ site.image | absolute_url }}" />
{% endif %}
<meta property="og:image:width" content="640" />
<meta property="og:image:height" content="640" />
<meta name="twitter:card" content="summary" />
<meta
name="twitter:title"
content="{% if page.title %}{{ page.title | escape }} | {{
site.author.twitter
}}{% else %}{{ site.title | escape }}{% endif %}"
/>
<meta
name="twitter:url"
content="{{
page.url | remove: 'index.html' | remove: '.html' | absolute_url
}}"
/>
<meta name="twitter:site" content="@{{ site.author.twitter }}" />
<meta name="twitter:creator" content="@{{ site.author.twitter }}" />
<meta
name="twitter:description"
content="{{
page.description
| default: site.description
| strip_html
| normalize_whitespace
| truncate: 200
| escape
}}"
/>
{% if page.image %}
<meta name="twitter:image" content="{{ page.image | absolute_url }}" />
{% else %}
<meta name="twitter:image" content="{{ site.image | absolute_url }}" />
{% endif %} {% feed_meta %}
<link rel="apple-touch-icon" sizes="180x180" href="/assets/favicons/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/assets/favicons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/assets/favicons/favicon-16x16.png">
<link rel="manifest" href="/assets/favicons/site.webmanifest">
<link rel="stylesheet" href="/assets/css/style.css" />
</head>

View File

@ -1,201 +0,0 @@
<div class="navbar" role="navigation">
<nav class="menu">
<input type="checkbox" id="menu-trigger" class="menu-trigger" />
<label for="menu-trigger">
<span class="menu-icon">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 512 512"
>
<path
d="M64,384H448V341.33H64Zm0-106.67H448V234.67H64ZM64,128v42.67H448V128Z"
/>
</svg>
</span>
</label>
<div class="trigger">
<div class="trigger-container">
{%- assign url = page.url -%}
{%- assign menus = site.data.menus -%}
{%- if menus %}
{%- for menu in menus -%}
{%- if url == menu.url -%}
<a class="menu-link active" href="{{ menu.url }}">{{ menu.title }}</a>
{%- else -%}
<a class="menu-link" href="{{ menu.url }}">{{ menu.title }}</a>
{%- endif -%}
{%- endfor -%}
{%- else -%}
<a class="menu-link {% if url == '/' %}active{% endif %}" href="/">home</a>
<a class="menu-link {% if url == '/about/' %}active{% endif %}" href="/about">about</a>
{%- endif -%}
<a class="menu-link rss" href="/feed.xml">
<svg
xmlns="http://www.w3.org/2000/svg"
width="17"
height="17"
viewBox="0 0 512 512"
fill="#ED812E"
>
<title>RSS</title>
<path
d="M108.56,342.78a60.34,60.34,0,1,0,60.56,60.44A60.63,60.63,0,0,0,108.56,342.78Z"
/>
<path
d="M48,186.67v86.55c52,0,101.94,15.39,138.67,52.11s52,86.56,52,138.67h86.66C325.33,312.44,199.67,186.67,48,186.67Z"
/>
<path
d="M48,48v86.56c185.25,0,329.22,144.08,329.22,329.44H464C464,234.66,277.67,48,48,48Z"
/>
</svg>
</a>
</div>
</div>
<a id="mode">
<svg
class="mode-sunny"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 512 512"
>
<title>LIGHT</title>
<line
x1="256"
y1="48"
x2="256"
y2="96"
style="stroke-linecap:round;stroke-miterlimit:10;stroke-width:32px"
/>
<line
x1="256"
y1="416"
x2="256"
y2="464"
style="stroke-linecap:round;stroke-miterlimit:10;stroke-width:32px"
/>
<line
x1="403.08"
y1="108.92"
x2="369.14"
y2="142.86"
style="stroke-linecap:round;stroke-miterlimit:10;stroke-width:32px"
/>
<line
x1="142.86"
y1="369.14"
x2="108.92"
y2="403.08"
style="stroke-linecap:round;stroke-miterlimit:10;stroke-width:32px"
/>
<line
x1="464"
y1="256"
x2="416"
y2="256"
style="stroke-linecap:round;stroke-miterlimit:10;stroke-width:32px"
/>
<line
x1="96"
y1="256"
x2="48"
y2="256"
style="stroke-linecap:round;stroke-miterlimit:10;stroke-width:32px"
/>
<line
x1="403.08"
y1="403.08"
x2="369.14"
y2="369.14"
style="stroke-linecap:round;stroke-miterlimit:10;stroke-width:32px"
/>
<line
x1="142.86"
y1="142.86"
x2="108.92"
y2="108.92"
style="stroke-linecap:round;stroke-miterlimit:10;stroke-width:32px"
/>
<circle
cx="256"
cy="256"
r="80"
style="stroke-linecap:round;stroke-miterlimit:10;stroke-width:32px"
/>
</svg>
<svg
class="mode-moon"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 512 512"
>
<title>DARK</title>
<line
x1="256"
y1="48"
x2="256"
y2="96"
style="stroke-linecap:round;stroke-miterlimit:10;stroke-width:32px"
/>
<line
x1="256"
y1="416"
x2="256"
y2="464"
style="stroke-linecap:round;stroke-miterlimit:10;stroke-width:32px"
/>
<line
x1="403.08"
y1="108.92"
x2="369.14"
y2="142.86"
style="stroke-linecap:round;stroke-miterlimit:10;stroke-width:32px"
/>
<line
x1="142.86"
y1="369.14"
x2="108.92"
y2="403.08"
style="stroke-linecap:round;stroke-miterlimit:10;stroke-width:32px"
/>
<line
x1="464"
y1="256"
x2="416"
y2="256"
style="stroke-linecap:round;stroke-miterlimit:10;stroke-width:32px"
/>
<line
x1="96"
y1="256"
x2="48"
y2="256"
style="stroke-linecap:round;stroke-miterlimit:10;stroke-width:32px"
/>
<line
x1="403.08"
y1="403.08"
x2="369.14"
y2="369.14"
style="stroke-linecap:round;stroke-miterlimit:10;stroke-width:32px"
/>
<line
x1="142.86"
y1="142.86"
x2="108.92"
y2="108.92"
style="stroke-linecap:round;stroke-miterlimit:10;stroke-width:32px"
/>
<circle
cx="256"
cy="256"
r="80"
style="stroke-linecap:round;stroke-miterlimit:10;stroke-width:32px"
/>
</svg>
</a>
</nav>
</div>

View File

@ -1,16 +0,0 @@
<nav class="post-nav">
{% if page.previous %}
<a
class="post-nav-item post-nav-prev"
href="{{ page.previous | relative_url }}"
>
<div class="nav-arrow">↶ Anterior</div>
<span class="post-title">{{ page.previous.title }}</span>
</a>
{% endif %} {% if page.next %}
<a class="post-nav-item post-nav-next" href="{{ page.next | relative_url }}">
<div class="nav-arrow">Siguiente ↷</div>
<span class="post-title">{{ page.next.title }}</span>
</a>
{% endif %}
</nav>

View File

@ -1,21 +0,0 @@
<!-- NOTE: unused file, but u can use if necessary -->
<!-- <div class="pagination">
{% if paginator.previous_page %}
<a
class="page-previous"
href="{{ paginator.previous_page_path }}"
class="previous"
>
<span aria-hidden="true">←</span> NEWER POSTS
</a>
{% endif %}
<span class="page_number"
>PAGE {{ paginator.page }} OF {{ paginator.total_pages }}</span
>
{% if paginator.next_page %}
<a class="page-next" href="{{ paginator.next_page_path }}" class="next"
>OLDER POSTS
<span aria-hidden="true">→</span>
</a>
{% endif %}
</div> -->

View File

@ -1,46 +0,0 @@
---
layout: compress
---
<!DOCTYPE html>
<html lang="{{ page.lang | default: site.lang | default: ' en ' }}">
{% include header.html %}
<body data-theme="{{ site.mode }}" class="notransition">
<script>
const body = document.body;
const data = body.getAttribute("data-theme");
const initTheme = (state) => {
if (state === "dark") {
body.setAttribute("data-theme", "dark");
} else if (state === "light") {
body.removeAttribute("data-theme");
} else {
localStorage.setItem("theme", data);
}
};
initTheme(localStorage.getItem("theme"));
setTimeout(() => body.classList.remove("notransition"), 75);
</script>
{% include navbar.html %}
<div class="wrapper">
<main aria-label="Content">
<div class="not-found">
<div class="container">
<div class="title">404</div>
<p class="phrase">😕 Hmm... Parece que aquí no hay nada...</p>
<a class="solution" href="{{ site.url }}">volver al inicio</a>
</div>
</div>
</main>
{% include footer.html %}
</div>
</body>
</html>

View File

@ -1,4 +0,0 @@
---
---
{% if site.compress_html.ignore.envs contains jekyll.environment %}{{ content }}{% else %}{% capture _content %}{{ content }}{% endcapture %}{% if site.compress_html.endings == "all" %}{% assign _endings = "html head body li dt dd p rt rp optgroup option colgroup caption thead tbody tfoot tr td th" | split: " " %}{% else %}{% assign _endings = site.compress_html.endings %}{% endif %}{% for _element in _endings %}{% capture _end %}</{{ _element }}>{% endcapture %}{% assign _content = _content | remove: _end %}{% endfor %}{% if site.compress_html.comments.size == 2 %}{% assign _comment_befores = _content | split: site.compress_html.comments.first %}{% for _comment_before in _comment_befores %}{% assign _comment_content = _comment_before | split: site.compress_html.comments.last | first %}{% if _comment_content %}{% capture _comment %}{{ site.compress_html.comments.first }}{{ _comment_content }}{{ site.compress_html.comments.last }}{% endcapture %}{% assign _content = _content | remove: _comment %}{% endif %}{% endfor %}{% endif %}{% assign _pre_befores = _content | split: "<pre" %}{% assign _content = "" %}{% for _pre_before in _pre_befores %}{% assign _pres = _pre_before | split: "</pre>" %}{% case _pres.size %}{% when 2 %}{% capture _content %}{{ _content }}<pre{{ _pres.first }}</pre>{{ _pres.last | split: " " | join: " " }}{% endcapture %}{% when 1 %}{% capture _content %}{{ _content }}{{ _pres.last | split: " " | join: " " }}{% endcapture %}{% endcase %}{% endfor %}{% if site.compress_html.clippings == "all" %}{% assign _clippings = "html head title base link meta style body article section nav aside h1 h2 h3 h4 h5 h6 hgroup header footer address p hr blockquote ol ul li dl dt dd figure figcaption main div table caption colgroup col tbody thead tfoot tr td th" | split: " " %}{% else %}{% assign _clippings = site.compress_html.clippings %}{% endif %}{% for _element in _clippings %}{% assign _edges = " <e;<e; </e>;</e>;</e> ;</e>" | replace: "e", _element | split: ";" %}{% assign _content = _content | replace: _edges[0], _edges[1] | replace: _edges[2], _edges[3] | replace: _edges[4], _edges[5] %}{% endfor %}{{ _content }}{% endif %}

View File

@ -1,38 +0,0 @@
---
layout: compress
---
<!DOCTYPE html>
<html lang="{{ page.lang | default: site.lang | default: " en " }}">
{% include header.html %}
<body data-theme="{{ site.mode }}" class="notransition">
<script>
const body = document.body;
const data = body.getAttribute("data-theme");
const initTheme = (state) => {
if (state === "dark") {
body.setAttribute("data-theme", "dark");
} else if (state === "light") {
body.removeAttribute("data-theme");
} else {
localStorage.setItem("theme", data);
}
};
initTheme(localStorage.getItem("theme"));
setTimeout(() => body.classList.remove("notransition"), 75);
</script>
{% include navbar.html %}
<div class="wrapper">
{% include author.html %}
<main aria-label="Content">
{{ content }}
</main>
</div>
{% include footer.html %}
</body>
</html>

View File

@ -1,14 +0,0 @@
---
layout: default
home: true
---
<h3 class="posts-item-note" aria-label="Recent Posts">Recent Posts ↓</h3>
{%- for post in site.posts limit: site.number_of_posts -%}
<article class="post-item">
<span class="post-item-date">{{ post.date | date: "%d %b %Y" }}</span>
<h4 class="post-item-title">
<a href="{{ post.url }}">{{ post.title | escape }}</a>
</h4>
</article>
{%- endfor -%}

View File

@ -1,43 +0,0 @@
---
layout: compress
---
<!DOCTYPE html>
<html lang="{{ page.lang | default: site.lang | default: " en " }}">
{% include header.html %}
<body data-theme="{{ site.mode }}" class="notransition">
<script>
const body = document.body;
const data = body.getAttribute("data-theme");
const initTheme = (state) => {
if (state === "dark") {
body.setAttribute("data-theme", "dark");
} else if (state === "light") {
body.removeAttribute("data-theme");
} else {
localStorage.setItem("theme", data);
}
};
initTheme(localStorage.getItem("theme"));
setTimeout(() => body.classList.remove("notransition"), 75);
</script>
{% include navbar.html %}
<div class="wrapper">
<header class="header">
<h1 class="header-title center" itemprop="headline">{{ page.title | escape }}.</h1>
</header>
<main class="page-content" aria-label="Content">
{% include anchor_headings.html html=content anchorClass="anchor-head" beforeHeading=true h_min=4 h_max=4 %}
</main>
</div>
{% include footer.html %}
</body>
</html>

View File

@ -1,94 +0,0 @@
---
layout: compress
---
<!DOCTYPE html>
<html lang="{{ page.lang | default: site.lang | default: "en" }}">
{% include header.html %}
<body data-theme="{{ site.mode }}" class="notransition">
<script>
const body = document.body;
const data = body.getAttribute("data-theme");
const initTheme = (state) => {
if (state === "dark") {
body.setAttribute("data-theme", "dark");
} else if (state === "light") {
body.removeAttribute("data-theme");
} else {
localStorage.setItem("theme", data);
}
};
initTheme(localStorage.getItem("theme"));
setTimeout(() => body.classList.remove("notransition"), 75);
</script>
{% include navbar.html %}
<div class="wrapper post">
<main class="page-content" aria-label="Content">
<article itemscope itemtype="https://schema.org/BlogPosting">
<header class="header">
{% if page.tags and page.tags != empty %}
<div class="tags">
{% assign tags = page.tags %}
<span itemprop="keywords">
{% for tag in tags %}
<a class="tag"
href="/tags/#{{tag | downcase | slugify}}">{{tag | upcase }}</a>{% unless forloop.last %},{% endunless %}
{% endfor %}
</span>
</div>
{% endif %}
<h1 class="header-title" itemprop="headline">{{ page.title | escape }}</h1>
{% if page.date %}
<div class="post-meta">
<time datetime="{{ page.date | date_to_xmlschema }}" itemprop="datePublished">
{{ page.date | date: "%d %b %Y" }}
</time>
<span itemprop="author" itemscope itemtype="https://schema.org/Person">
<span itemprop="name">{{ site.author.name }}</span>
</span>
<time hidden datetime="{{ page.modified | date_to_xmlschema }}" itemprop="dateModified">
{{ page.date | date: "%b %d, %Y" }}
</time>
<span hidden itemprop="publisher" itemtype="Person">{{ site.author.name }}</span>
<span hidden itemprop="image">{{ page.image }}</span>
<span hidden itemprop="mainEntityOfPage">{{ page.excerpt }}</span>
</div>
{% endif %}
</header>
<div class="page-content" itemprop="articleBody">
{% include anchor_headings.html html=content anchorClass="anchor-head" beforeHeading=true h_min=1 h_max=4 %}
{% if page.tweet %}
<p>Comments this article on
<a href="https://twitter.com/{{site.twitter}}/status/{{page.tweet}}">Twitter</a>.
</p>
{% endif %}
</div>
</article>
{% if page.comments %}
{% include comments.html %}
{% endif %}
</main>
{% if page.modified %}
<small class="post-updated-at">updated_at {{page.modified | date: "%d-%m-%Y"}}</small>
{% endif %}
{% if page.next or page.previous %}
{% include navigation.html %}
{% endif %}
</div>
{% include footer.html %}
</body>
</html>

View File

@ -1,19 +1,20 @@
--- ---
layout: post layout: post
title: "Hola!" title: "Hola!"
tags: [jekyll, python, server, md, markdown]
--- ---
Primer post, sin mucho que contar para variar. Al menos habrá un bonito _commit_ y muchas cosas por limpiar. Primer post, sin mucho que contar para variar. Al menos habrá un bonito _commit_ y muchas cosas por limpiar.
La chuleta para hacer andar esto es la siguiente: La chuleta para hacer andar esto es la siguiente:
```shell ```bash
docker run -it --rm --volume="$PWD:/srv/jekyll:Z" jekyll/jekyll jekyll build docker run -it --rm --volume="$PWD:/srv/jekyll:Z" jekyll/jekyll jekyll build
docker run -it --rm --volume="$PWD:/srv/jekyll:Z" -p 4000:4000 jekyll/jekyll jekyll serve docker run -it --rm --volume="$PWD:/srv/jekyll:Z" -p 4000:4000 jekyll/jekyll jekyll serve
``` ```
O bien, si ya está _buildeado_: O bien, si ya está _buildeado_:
```shell ```bash
cd _site cd _site
python3 -m http.server 4000 python3 -m http.server 4000
``` ```

View File

@ -1,6 +1,7 @@
--- ---
layout: post layout: post
title: "CI/CD con Gitea + Drone" title: "CI/CD con Gitea + Drone"
tags: [fit, gitea, drone, cicd, docker, registry, runner, nginx]
--- ---
[Drone CI](https://www.drone.io/) es un sistema de CI/CD autónomo y autogestinado que puede asemejarse a lo que es Gitlab CI, Github Actions, Travis CI, etc. En mi caso, al usar [Gitea](https://gitea.io/), me viene perfecto para dar soporte de CI a mis proyectos. [Drone CI](https://www.drone.io/) es un sistema de CI/CD autónomo y autogestinado que puede asemejarse a lo que es Gitlab CI, Github Actions, Travis CI, etc. En mi caso, al usar [Gitea](https://gitea.io/), me viene perfecto para dar soporte de CI a mis proyectos.
@ -14,12 +15,14 @@ Y todo "privado".
El ejemplo es este mismo blog. Cuando creo/edito una entrada, o literalmente cualquier archivo del mismo, al _commitear_ los cambios a _git_ se ejecuta un _pipeline_ en el runner que corresponda, reconstruye el sitio y crea una imágen del resultado final, lo manda al registro, y se despliega la nueva imágen. Con prácticamente 0 downtime, rollbacks, versionado y código 100% visible y disponible. El ejemplo es este mismo blog. Cuando creo/edito una entrada, o literalmente cualquier archivo del mismo, al _commitear_ los cambios a _git_ se ejecuta un _pipeline_ en el runner que corresponda, reconstruye el sitio y crea una imágen del resultado final, lo manda al registro, y se despliega la nueva imágen. Con prácticamente 0 downtime, rollbacks, versionado y código 100% visible y disponible.
> Nota: Esto sería en un escenario ideal y así estaba en la anterior versión, pero en la actual no se monta una imágen aun, se monta el resultado del sitio en el _host_. Eso cambiará a futuro.
El resumen del archivo [.drone.yml](https://sergio.am/code/sergio.am/src/branch/main/.drone.yml) sería: El resumen del archivo [.drone.yml](https://sergio.am/code/sergio.am/src/branch/main/.drone.yml) sería:
1. Bla 1. _build_ del sitio con la [imágen de docker de Jekyll](https://hub.docker.com/r/jekyll/jekyll/).
2. blo 2. _build_ de la imágen final basada en nginx (por ejemplo) copiando el contenido de `_site`.
3. lala 3. _push_ de la imágen al registro.
4. lslslsls 4. _pull_ del registro y _restart_ de la nueva imágen donde se esté ejecutando.
## Web server ## Web server
@ -27,8 +30,17 @@ Como el contenido hay que servirlo desde alguna parte, tengo la siguiente config
```nginx ```nginx
server { server {
... server_name sergio.am;
... root /var/www/_site;
index index.html;
location / {
try_files $uri $uri/ @gitea;
}
location @gitea {
proxy_pass http://gitea:3000;
}
} }
``` ```

View File

@ -1,6 +1,7 @@
--- ---
layout: post layout: post
title: "Conectividad IPv6 sobre túnel WireGuard y OpenWRT" title: "Conectividad IPv6 sobre túnel WireGuard y OpenWRT"
tags: [openwrt, wireguard, vpn, tunnel, 6in4, tunnelbroker]
--- ---
La situación actual en España es que ningún operador de internet da IPv6. Y yo quiero IPv6. No por nada especial, simplemente por tener esa alternativa de conexión, probar cosas, aprender, etc. Y como lo quiero, lo he hecho, ejemplo de petición a [ipv6.google.com](https://ipv6.google.com/): La situación actual en España es que ningún operador de internet da IPv6. Y yo quiero IPv6. No por nada especial, simplemente por tener esa alternativa de conexión, probar cosas, aprender, etc. Y como lo quiero, lo he hecho, ejemplo de petición a [ipv6.google.com](https://ipv6.google.com/):
@ -22,6 +23,58 @@ Aunque mi solución no es perfecta tampoco (mirad el ping), solo por la estabili
## VPS barato + WireGuard + OpenWRT ## VPS barato + WireGuard + OpenWRT
Esta es mi solución: crear un tunel seguro con [WireGuard](https://www.wireguard.com/) entre un VPS cualquiera y el router con OpenWRT. Si has llegado hasta aquí no netesito decirte por qué WireGuard. El router hace de cliente, y el VPS de servidor. Esta es mi solución: crear un tunel seguro con [WireGuard](https://www.wireguard.com/) entre un VPS cualquiera y el router con OpenWRT. Si has llegado hasta aquí no necesito decirte por qué WireGuard. El router hace de cliente, y el VPS de servidor. Configuración standart. La clave de sacar todo el tráfico IPv6 por el tunel es el siguiente, `/etc/config/network`:
blablabla... ```
config interface 'WG'
option proto 'wireguard'
option listen_port '51820'
option private_key 'priv.key...'
list addresses '10.11.12.2/24'
list addresses 'fdbc:f607:7749::2/64'
config wireguard_WG
option description 'WireGuard'
option endpoint_port '51820'
option route_allowed_ips '1'
option public_key 'pub.key...'
option endpoint_host '1.2.3.4'
option persistent_keepalive '25'
list allowed_ips '10.11.12.0/24'
list allowed_ips '::/0'
```
El segundo `list addresses` del _interface_, y el `list allowed_ips`. Hay que darle al interface una IPv6 en el servidor también:
```ini
[Peer]
# Name = peer_openwrt
PublicKey = pub.key...
AllowedIPs = 10.11.12.2/32,fdbc:f607:7748::2,fdb9:f2e5:ccc0::/48
```
Además el interface del servidor necesita IPv6 también:
```ini
[Interface]
Address = 10.11.12.1/24, fdbc:f607:7748::1/64
```
Y las reglas de `iptables` para IPv4 y 6:
```ini
PostUp = iptables -A FORWARD -i %i -j ACCEPT
PostUp = iptables -A FORWARD -o %i -j ACCEPT
PostUp = ip6tables -A FORWARD -i %i -j ACCEPT
PostUp = ip6tables -A FORWARD -o %i -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostUp = ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PreDown = iptables -D FORWARD -i %i -j ACCEPT
PreDown = iptables -D FORWARD -o %i -j ACCEPT
PreDown = ip6tables -D FORWARD -i %i -j ACCEPT
PreDown = ip6tables -D FORWARD -o %i -j ACCEPT
PreDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
PreDown = ip6tables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
```
"Y ya está"! 3 años para conseguir que esto funcione, para que a las dos semanas cambie de operadora y me den IPv6 nativo 🥲

View File

@ -0,0 +1,71 @@
---
layout: post
title: "Digi: Remplazar router y ONT ZTE por OpenWRT y Ufiber"
tags: [digi, zte, ufiber, loco, openwrt, linksys, wrt3200acm]
---
La mayor parte del procedimiento se consigue siguiendo este link de Bandaancha: [Cómo cambiar la ONT ZTE F601 v7 por un Ufiber Loco sobre fibra Digi](https://bandaancha.eu/foros/como-cambiar-zte-f601-v7-ufiber-loco-1743790) ([cache](/assets/digi-openwrt-ont/Cómo cambiar la ONT ZTE F601 v7 por un Ufiber Loco sobre fibra Digi 💡.pdf)). Además he replicado [este post allí también](https://bandaancha.eu/foros/remplazar-router-zte-h3600-uno-openwrt-1746083).
Digi, a día de hoy, uno de los equipos que instala para su fibra Smart es el router ZTE ZXHN H3600 ([manual](/assets/digi-openwrt-ont/ZTE_ZXHN_H3600_Manual_de_usuario.pdf) y [guia](/assets/digi-openwrt-ont/Guia-FIBRA_ZTE_H3600_WiFi6.pdf)) + ONT ZTE F601 v7 ([manual](/assets/digi-openwrt-ont/ZXHN-F601-PON-ONT-User-Manual.pdf)).
Por lo mismo que dicen ahí, prefiero tener mis equipos, que para eso los pillé en su día. Controlas las actualizaciones, las configuraciones, etc.
![ont](/assets/digi-openwrt-ont/ont.png)
Lo que yo tengo ahora mismo es:
- ONT [Ubiquiti UFiber Loco](https://store.ui.com/collections/operator-ufiber/products/ufiber-loco) (vaya nombre)
- Hardware: v1.0
- Firmware: 4.4.1
- Router [Linksys WRT3200ACM](https://www.linksys.com/es/wireless-routers/wrt-wireless-routers/linksys-wrt3200acm-ac3200-mu-mimo-gigabit-wifi-router/p/p-wrt3200acm/)
- Hardware: ARMv7 Processor rev 1 (v7l)
- Firmware: OpenWrt 21.02.0 r16279-5cc0535800 / LuCI openwrt-21.02 branch git-21.231.26241-422c175.
Ok, nada del otro mundo.
Una vez te ponen la fibra, llamas para que te envie los datos PPPoE y te saquen del CG-NAT (1€ al mes). Te mandan los datos y configuramos OpenWRT.
Esto es solo para poder acceder al ONT desde la LAN. El ONT lo tengo en la IP 192.168.100.1:
```
config interface 'ONT'
option proto 'static'
option device 'wan'
option ipaddr '192.168.100.2'
option netmask '255.255.255.0'
```
En `Network > Interface > Devices` creamos uno nuevo para la VLAN de Digi, la `20`:
```
config device
option type '8021q'
option ifname 'wan'
option vid '20'
option name 'wan.20'
```
![vlan](/assets/digi-openwrt-ont/vlan.png)
Ya solo queda configurar la WAN para que se identifique contra la OLT:
```
config interface 'wan'
option proto 'pppoe'
option username '1234567@digi'
option password 'PaSS'
option device 'wan.20'
```
En `username` y `password` ponemos lo que nos ha mandado Digi por correo.
Y ya está, IPv6 de forma nativa...
![ipv6](/assets/digi-openwrt-ont/ipv6.png)
Buenos tests (ya daba por hecho que no iba a llegar al Gb, esta mañana si he estado en >900Mbps):
![speedtest](/assets/digi-openwrt-ont/speedtest.png)
Y todo esto por 26€, comparado a los 54 de Movistar y sus 600Mbps, teléfono fijo obligatorio, y OLT (en mi zona) Alcatel que no permite poner otra OLT que no sea Alcatel. Eso si, Movistar ha funcionado perfecto este año y medio, no voy a negarlo. Y de 10 las gestiones para darlo de alta de un amigo. PERO, 54€...
Aun no tengo la portabilidad del móvil completada, pero Digi usa la red de Movistar y dudo que aprecie diferencias.

View File

@ -0,0 +1,27 @@
---
layout: post
title: "Clasificar clientes con diferentes opciones DHCP en OpenWRT"
tags: [openwrt, dhcp, tag, ip, iphone, android, pihole, dns]
---
Si en tu red local usas cosas como [Pi-hole](https://pi-hole.net/) para filtrar los DNS y bloquear publicidad/malware/etc, y por casualidad tienes algún dispositivo iOS (Apple), vas a tener una notificación permanente de que "La red no es segura". Algo tiene que ver el [DoH](https://en.wikipedia.org/wiki/DNS_over_HTTPS), que se ve que viene activado por defecto, y al "falsear los DNS" se entera.
Mi solución ha sido drástica: los iPhone no usan Pi-hole en mi red. Para ello hay que crear un _tag_ con las opciones pertinentes, y un Static Lease tageando ese dispositivo. Lo explican en la documentación de OpenWRT: [Client classifying and individual options](https://openwrt.org/docs/guide-user/base-system/dhcp_configuration#client_classifying_and_individual_options) (no se puede hacer via admin, no tienen implementados los _tags_):
```shell
uci set dhcp.nopi="tag"
uci set dhcp.nopi.dhcp_option="6,8.8.8.8,1.1.1.1"
uci add dhcp host
uci set dhcp.@host[-1].name="pepito-iphone"
uci set dhcp.@host[-1].mac="AA:BB:CC:DD:FF:11"
uci set dhcp.@host[-1].ip="192.168.1.20"
uci set dhcp.@host[-1].tag="nopi"
uci commit dhcp
/etc/init.d/dnsmasq restart
```
No he probado a no darle in IP concreta, seguramente no haga ni falta.
Se reconecta a la WiFi y pista, nuevas opciones DHCP para él solito, y aquí no ha pasado nada.

View File

@ -0,0 +1,97 @@
---
layout: post
title: "MagicMirror con sensor de movimiento"
---
Un MagicMirror no es más que un "espejo" con una pantalla detrás que muestra información "impresa" en el espejo. Y digo "espejo" porque esto no se hace con un espejo normal, sino con un vinilo unidireccional, como el de los cristales de las salas de interrogatorios de las pelis.
Para que este vinilo funcione se necesita un requisito imprescindible: que la parte de detrás del reflejo sea oscuro, o más escruto que lo de en frente. Si este vinilo lo pones en una ventana normal, de día verás tu reflejo al haber más luz fuera que dentro, pero si iluminas el interior, o fuera es de noche, se ve todo el interior. O gran parte.
He aprovechado una RaspberryPi 3b+ que no le daba uso ya para hacer este tinglado. Lo que se necesita, a grandes rasgos:
### Una raspi
Recomiendo usar Raspbian de 64bits. El resto de la instalación es standart. Le instalamos [MagicMirror](https://magicmirror.builders/) como dicen la documentación. No entraré en detalles de eso, no he hecho nada fuera de lo común.
### Un monitor/pantalla
Voy a usar un monitor viejo, solo tiene DVI así que le he puesto un adaptador HDMI-DVI. Lo he desmontado/destripado y me quedaré solo con la pantalla en si y la circuitería.
### Sensor de movimiento
Para encender/apagar la pantalla. No tiene sentido tenerla encendida el 100% del tiempo, solo cuando vea que me acerco. [He pillado este](https://www.amazon.es/gp/product/B07CNBYRQ7/ref=ppx_yo_dt_b_asin_title_o05_s00?ie=UTF8&th=1). Cableado:
![wiring](/assets/magicmirror/pir_wiring.png)
Yo he usado [los pines 4, 6 y 8](https://gpiozero.readthedocs.io/en/stable/recipes.html#pin-numbering), por quedar juntos. El pin 8 es el GPIO14.
[Mucha info sobre cómo usar el PIR con la Raspi](https://projects.raspberrypi.org/en/projects/physical-computing/11).
### Cables
Jumper cables, un alargo, un ladrón. Hay que conectar el sensor a la raspi, la raspi y el monitor a la luz, la raspi a la pantalla, etc. Cables por todos lados.
# Configuración de la Raspi
A parte [meter en el arranque el MM²](https://docs.magicmirror.builders/configuration/autostart.html) y de [desactivar el protector de pantalla](https://github.com/MichMich/MagicMirror/wiki/Configuring-the-Raspberry-Pi#disabling-the-screensaver), he tenido que usar `vcgencmd` en vez de `tvservice`:
```
tvservice is not supported when using the vc4-kms-v3d driver.
Similar features are available with standard linux tools
such as modetest from libdrm-tests.
```
Se podríá usar otro driver, pero entonces Electron se come la raspi.
Pero es lo mismo, `vcgencmd display_power 0` para apagar la pantalla, `1` para encender. [Montones de info aquí](https://forum.magicmirror.builders/topic/6291/howto-turn-on-off-your-monitor-time-based-pir-button-app).
# `pir.py`
Esto leerá el sensor y controlará la pantalla en consecuencia:
```python
#! /usr/bin/python
import os
import time
from gpiozero import MotionSensor
from datetime import datetime
pir = MotionSensor(14)
print('up!')
while True:
pir.wait_for_motion()
os.system('vcgencmd display_power 1')
pir.wait_for_no_motion()
os.system('vcgencmd display_power 0')
```
A parte, para que estoy quede fino, he configurado el sensor en modo H (re-trigger), que viene a ser que si el sensor está UP, y vuelve a haber movimiento, se resetea el tiempo de espera. [En la docu del sensor lo explican](/assets/magicmirror/Bewegungsmelder_Modul_Datenblatt.pdf) (o algo así).
Si en tu red local usas cosas como [Pi-hole](https://pi-hole.net/) para filtrar los DNS y bloquear publicidad/malware/etc, y por casualidad tienes algún dispositivo iOS (Apple), vas a tener una notificación permanente de que "La red no es segura". Algo tiene que ver el [DoH](https://en.wikipedia.org/wiki/DNS_over_HTTPS), que se ve que viene activado por defecto, y al "falsear los DNS" se entera.
Mi solución ha sido drástica: los iPhone no usan Pi-hole en mi red. Para ello hay que crear un _tag_ con las opciones pertinentes, y un Static Lease tageando ese dispositivo. Lo explican en la documentación de OpenWRT: [Client classifying and individual options](https://openwrt.org/docs/guide-user/base-system/dhcp_configuration#client_classifying_and_individual_options) (no se puede hacer via admin, no tienen implementados los _tags_):
```shell
uci set dhcp.nopi="tag"
uci set dhcp.nopi.dhcp_option="6,8.8.8.8,1.1.1.1"
uci add dhcp host
uci set dhcp.@host[-1].name="pepito-iphone"
uci set dhcp.@host[-1].mac="AA:BB:CC:DD:FF:11"
uci set dhcp.@host[-1].ip="192.168.1.20"
uci set dhcp.@host[-1].tag="nopi"
uci commit dhcp
/etc/init.d/dnsmasq restart
```
No he probado a no darle in IP concreta, seguramente no haga ni falta.
Se reconecta a la WiFi y pista, nuevas opciones DHCP para él solito, y aquí no ha pasado nada.

View File

@ -0,0 +1,65 @@
---
layout: post
title: "Mi plan de backups"
tags: [backup, linux, windows, cloud, storage]
---
Digamos que todo lo que hice con ordenadores hasta mis 20ymedios, lo perdí. Por entonces no me preocupaba mucho perder cosas que hacía, no le daba valor, y hoy en día me arrepiento. Por eso desde hace casi 10 años, una de mis obsesiones es no perder información. Información útil, claro. Documentos, imágenes, etc. A día de hoy guardo casi todo, y de una forma ordenada.
En este mundillo hay una cosa que se llama la regla del 3-2-1:
- Hacer al menos 2 copias de los datos.
- Tener esas copias en diferentes medios.
- Al menos 1 de las copias tenerla offsite.
Esto yo lo veo orientativo más que como algo mandatorio. En mi caso es todo muy manual y casero.
# De qué hago backup
### Servidores
Tengo unos cuantos VPS y Dedicados para diferentes finalidades, la mayoría no son cosas mias sino proyectos de amigos o conocidos. Hay cosas como las bases de datos que mejor hacer dump en vez de copiar los archivos internos. Mantengo documentando y versionado en gran medida todo.
### Cacharritos
RaspberryPis, NASes, incluso una retro consola portátil hecha con un NUC. Todos con su backup.
### Equipos
Principalmente el PC de casa y el portátil del curro. No hago copia de tooodo el equipo en este caso, solo de las _homes_ y algún directorio suelto (vease, `/etc`).
### Servicios
Aunque ya uso Google Drive para hacer backup de ciertas cosas, a su vez hago backup de Google Drive. SI, YA SÉ, Google no va a perder datos, pero si que puedo perder la cuenta, o que la cierren el día del juicio final (esto da para otro post).
Backup de Google Drive, Gmail, y mis correos de dominios personales (no los tengo en Gmail). Al igual que tengo servidores de otra gente, también sus dominios con sus correos. También hago backup de otros servicios de almacenamiento que uso para cosas secundarias, como box.com, OneDrive o Yandex Disk.
# Cómo hago los backups
Scripts. Si, hay software que hace ya las copias incluso luego te permite recuperar, pero es mayor el dolor de cabeza cuando falla o lo actualizan y cambian el funcionamiento. Así que con el tiempo he terminado haciéndome mis scripts, versionados y distribuidos por cada equipo. Esta medida me ha resultado siempre menos dolorosa.
Cada uno tiene si script/configuración, aunque algunos se parezcan o compartan funcionamiento. Todos van de forma similar, con un cron que por las noches (o varias veces al día) copian en la propia máquina lo que me interesa.
Luego, una vez al día envían esa copia a un sitio común. Aquí tenemos la doble copia ya, y en la mayoría de casos el offsite.
En la propia máquina solo tengo **la copia más reciente**.
Para servicios en la nube uso [`rclone`](https://rclone.org/) con el módulo crypt y para el correo [`imap-backup`](https://github.com/joeyates/imap-backup).
> Algún día escribiré sobre `rclone` porque es una maravilla. [Union](https://rclone.org/union/)FS es pura magia, y puedes tener teras por la cara usando esto.
# A donde mando los backups y qué hago con ellos
La copia de cada backup va a un pozo sin fondo de almacenamiento que puede contener todo. Todo entra aquí. En este pozo sin fondo tengo solo **1 semana de copias** de todo. Básicamente es un [Storage Box de Hetzner](https://www.hetzner.com/storage/storage-box). Antes usaba S3 de AWS y Glacier, pero me salía un poco más caro y era más coñazo de mantener desde fuera.
Mantengo sincronizado este almacenamiento a [Backblaze](https://backblaze.com) y a un raid físico en un búnker. Bueno, igual es exagerado, pero no anda lejos.
De nuevo tengo ya una tercera copia en 2 offsites adicionales. Backblaze cuesta dos duros el _tera_, y los discos del raid son grandes y lentos, espero que no fallen NUNCA. Y si fallan pues los reeamplazo por otro disco grande y lento, que valen otros dos duros.
En estos dos almacenamientos finales guardo **historicos variables, desde solo 1 mes, a infinito**. Depende del qué. El 90% de las copias es solo 1 mes, unos pocos 1 años, y algún caso MUY concreto, infinito.
# El desastre
Ok, y cuando algo falla, se pierde, o quiero recuperarlo, qué? A manita. Da igual que pueda recuperarlo de la copia _in situ_ a que tenga que traerla del almacenamiento final. Me la traigo de vuelta y recupero lo que sea.
Si es un equipo completo lleva más tiempo, porque hay más detallitos, pero no es mucho lio. En 1-2h lo tengo listo y volviedo a funcionar.

View File

@ -0,0 +1,115 @@
---
layout: post
title: "Abrir puertos con IPv6 dinámica en OpenWRT"
tags: [abrir, puertos, openwrt, ipv6, digi, port, forwarding, traffic, rules, dyndns, docker]
---
Con IPv6 no usamos NAT, por lo tanto no existe redirección de puertos (Port Forwarding). Hay tantas IPs disponibles que incluso los equipos dentro de la LAN tienen una IP "pública". Lo que nos suelen dar las operadoras/proveedores de internet es un rango de IPs, un `/64` es lo más común, por lo tanto tenemos la opción de alojar 2<sup>64</sup> (18,446,744,073,709,551,616) direcciones en nuestra red. Ese rango `/64` corresponde por convenio a una LAN. Si lo que tenemos es un servidor (dedicado normalmente, con los VPS no pasa) es posible que nos den una `/56` (256 LANs) o una `/48` (65,536 LANs).
Ejemplo con IPv4:
- IP de casa desde fuera: `1.2.3.4`.
- A su vez el router desde dentro es la `192.168.1.1` (puerta de enlace).
- El resto de equipos de la LAN estarán en el rango `/24` normalmente (hasta la `192.168.1.255`).
- Para abrir un puerto le decimos al router que el puerto X lo mande al puerto Y de una de las IPs de dentro.
Ok, ahora con IPV6 tenemos lo siguiente:
- IP de casa desde fuera `2001:2:3:4::/64` (seguramente veamos la `2001:2:3:4::2`).
- El router dará a cada equipo conectado una IP de ese rango, la `::2` sería el router en si.
- Para evitar colisiones chugas lo típico es usar la MAC del equipo conectado para formar la IPv6, por ejemplo quedaría algo como `2001:2:3:4:5:6:7:8`.
- Para abrir un puerto solo le decimos al router "permite el tráfico de la IP Z al puerto X". Incluso podemos obviar el puerto, pero no es recomendable.
Dicho lo cual, si queremos "abrir" un puerto a un equipo de nuestra LAN gestionada con OpenWRT tenemos que recurrir a las [Reglas de Tráfico (Traffic Rules)](https://openwrt.org/docs/guide-user/firewall/fw3_configurations/fw3_ipv6_examples). El tráfico va a venir directamente a una IP "de dentro", por lo tanto podemos servir el mismo puerto 80 (por ejemplo) con la misma conexión desde diferentes máquinas. En la práctica serán dos direcciones diferentes, pero antes con IPv4 no podíamos hacer fácilmente esto, ya que la IP de entrada era la misma.
Esto está bien, pero al igual que con IPv4 se complica levemente con IP dinámica. No mucho. Por suerte OpenWRT implementa una forma de especificar IPs sin conocer el prefijo, la parte `/64` que nos asigna la operadora.
Puesto que el sufijo será constante mientras se mantenga la MAC, sabemos que aunque cambie la IP de la operadora el sufijo se mantiene. En el caso de antes de una IP `2001:2:3:4:5:6:7:8`, la IP asignada por la operadora es `2001:2:3:4::/64` y ese equipo concreto siempre empezará por lo que nos asignen y terminará por `5:6:7:8`, así que podemos asignar una regla a la IP `::5:6:7:8/-64`.
BOOM!
Vamos con un ejemplo real, con una RaspberryPi que tengo en casa.
- IPv6 de casa: `2a0c:5a80:2301:2d00::/64` (de Digi, quienes tienen asignado, entre otros, un peazo rango `2a0c:5a80::/29` como [AS57269](https://db-ip.com/as57269-digi-spain-telecom-slu))
- OpenWRT me pilla la `2a0c:5a80:2302:2d00::2`.
- A la Raspi le asigna la `2a0c:5a80:2301:2d00:243e:d63d:943f:d8c1`.
De primeras, un escaneo de puertos a cualquiera de esas IPs da todo cerrado: `nmap -6 -sV 2a0c:5a80:2301:2d00:243e:d63d:943f:d8c1`. Pero vamos a permitir el tráfico en OpenWRT:
```shell
uci add firewall rule
uci set firewall.@rule[-1].target="ACCEPT"
uci set firewall.@rule[-1].src="wan"
uci set firewall.@rule[-1].dest="lan"
uci set firewall.@rule[-1].dest_ip="::243e:d63d:943f:d8c1/-64"
uci set firewall.@rule[-1].dest_port="22"
uci set firewall.@rule[-1].family="ipv6"
uci set firewall.@rule[-1].proto="tcp udp"
uci set firewall.@rule[-1].name="ssh6-raspi2"
uci commit firewall
/etc/init.d/firewall restart
````
O desde el interface en Network > Firewall > Traffic Rules...
![Firewall](/assets/traffic-rules/firewall.png)
Así quedaría la línea de la regla una vez añadida:
![Rule](/assets/traffic-rules/rule.png)
Volvemos a mirar los puertos y...:
```shell
$ nmap -6 -sV 2a0c:5a80:2301:2d00:243e:d63d:943f:d8c1
Starting Nmap 7.80 ( https://nmap.org ) at 2022-06-27 11:48 UTC
Nmap scan report for 2a0c:5a80:2301:2d00:243e:d63d:943f:d8c1
Host is up (0.032s latency).
Not shown: 999 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.4p1 Raspbian 5+b1 (protocol 2.0)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
```
Y si intentamos usarlo, efectivamente...
```shell
$ ssh -T piuser@2a0c:5a80:2301:2d00:243e:d63d:943f:d8c1
piuser@2a0c:5a80:2301:2d00:243e:d63d:943f:d8c1: Permission denied (publickey).
```
**Funciona**. De la misma forma, si intentamos conectar a cualquier otra IP, incluida la `::2` del router, no funcionará porque no hemos permitido ese tráfico. Tendremos que hacer esto para cada puerto que queramos abrir, de cualquier IP de la LAN.
# DynDNS
Aquí se presenta un problema. Bueno, dos. Si instalas algo tipo [ddclient](https://github.com/ddclient/ddclient), la IP que llevará el dominio que actualices irá con la IP de ese dispositivo. Por eso, la solución que he tenido es instalar uno en cada IP que quiero que tenga acceso desde fuera.
El segundo problema es que no todos los proveedores de DynDNS tienen soporte para IPv6. Así que buscando he terminado usando algo muy sencillito, [dynv6](https://dynv6.com). Un simple cron y a correr.
# Bonus: Docker
Docker es un poco asá con IPv6. Técnicamente tiene soporte pero no fue diseñado para ello. Mejor dicho, está diseñado para usar NAT, ya que directamente crea una VLAN interna para los contenedores. Así que con IPv6 la cabra tira para el monte.
Si se tiene una instalación limpia se puede hacer funcionar sin mayor problema, añadiendo lo siguiente al `/etc/docker/daemon.json`:
```json
{
"experimental" : true,
"ipv6":true,
"fixed-cidr-v6":"fd00:fe0:b0b0:dead::/80",
"ip6tables":true
}
```
Y `sudo systemctl restart docker.service`. La IPv6 me la invento, ya que será la IP que dará a los contenedores y luego internamente hará sus pirulas/NATing. Solo con esto me ha funcionado:
```shell
$ docker run --rm -it busybox ping6 sergio.am
PING sergio.am (2a06:98c1:3121::5): 56 data bytes
64 bytes from 2a06:98c1:3121::5: seq=0 ttl=56 time=3.384 ms
64 bytes from 2a06:98c1:3121::5: seq=1 ttl=56 time=3.213 ms
```
PERO, en el servidor he tenido que recurrir a una ñapa terrible para que funcionse, [docker-ipv6nat](https://github.com/robbertkl/docker-ipv6nat). Mientras las cosas esten como están en docker, eso es necesario.

View File

@ -0,0 +1,91 @@
---
layout: post
title: "LVM"
tags: [lvm, disk, discos, partición, volumen, lógico, raspberry pi]
---
[LVM](https://en.wikipedia.org/wiki/Logical_Volume_Manager_(Linux)) es una de esas cosas que siempre he visto pero nunca me paré siquiera a ver para qué podía ser útil. Casualmente ahora le he encontrado utilidad de rebote.
Tengo una Raspberry Pi 4 con un disco mecánico (lento) de unos cuantos teras, y otros 3 SSD de diferentes tamaños. Mi idea inicial era usar al menos uno de los SSD para hacer de caché del HDD con [bcache](https://en.wikipedia.org/wiki/Bcache), pero _oh sorpresa_ el kernel de Raspbian no lo trae activado por defecto. Ok, pues recompilo un kernel con eso activado... o no. Mientras compilaba he pensado en el mantenimiento de eso con cada actualización y he pensado "ni de coña pongo mi kernel a estas alturas de la vida".
Buscando alternativas me topo con LVM. No tiene que ver con caché, pero me sirve para lo que busco, que es un paso intermedio del HDD para descargas. Así que he hecho un volumen con dos SSD, para tener espacio suficiente entre descargas, y de ahí pasar al HDD:
```shell
# instalamos lvm2
$ sudo apt install lvm2
# creamos el volumen físico
$ sudo pvcreate /dev/sdb1 /dev/sdc1
WARNING: ext4 signature detected on /dev/sdb1 at offset 1080. Wipe it? [y/n]: y
Wiping ext4 signature on /dev/sdb1.
WARNING: ext4 signature detected on /dev/sdc1 at offset 1080. Wipe it? [y/n]: y
Wiping ext4 signature on /dev/sdc1.
Physical volume "/dev/sdb1" successfully created.
Physical volume "/dev/sdc1" successfully created.
$ sudo pvs
PV VG Fmt Attr PSize PFree
/dev/sdb1 lvm2 --- 59.62g 59.62g
/dev/sdc1 lvm2 --- <111.79g <111.79g
```
```shell
# creamos el grupo de volumen
$ sudo vgcreate ssd_vol /dev/sdb1 /dev/sdc1
Volume group "ssd_vol" successfully created
$ sudo vgs
VG #PV #LV #SN Attr VSize VFree
ssd_vol 2 0 0 wz--n- <171.38g <171.38g
```
```shell
# creamos el volumen lógico
$ sudo lvcreate -l 100%FREE -n ssd_log_vol ssd_vol
Logical volume "ssd_log_vol" created.
$ sudo lvs
LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert
ssd_log_vol ssd_vol -wi-a----- <171.38g
```
```shell
# formateamos el volúmen en ext4 y montamos la unidad
$ sudo mkfs.ext4 /dev/ssd_vol/ssd_log_vol
$ sudo mkdir /mnt/lvm_ssd
$ sudo chown -R pi:pi /mnt/lvm_ssd
$ sudo chmod 775 /mnt/lvm_ssd
$ sudo blkid
...
/dev/sdb1: UUID="L93uQY-2h0N-PJpU-T2lL-NDjc-Am38-1L12jm" TYPE="LVM2_member" PARTUUID="28d1fe50-01"
/dev/sdc1: UUID="ZMaG00-WNI1-jH3R-ycD1-mlwo-cB7M-0fNrTp" TYPE="LVM2_member" PARTUUID="74523e87-01"
/dev/mapper/ssd_vol-ssd_log_vol: UUID="10ff0f01-78dd-47e9-bff3-6ed738e680cf" BLOCK_SIZE="4096" TYPE="ext4"
# añadir al final
$ sudo nano /etc/fstab
UUID=10ff0f01-78dd-47e9-bff3-6ed738e680cf /mnt/lvm_ssd ext4 defaults,noatime,commit=30 0 1
$ sudo mount /mnt/lvm_ssd
# test de velocidad
$ dd if=/dev/zero of=/mnt/lvm_ssd/test.img bs=1M count=256 oflag=dsync
268435456 bytes (268 MB, 256 MiB) copied, 3.22186 s, 83.3 MB/s
```
```shell
# como ejemplo, pasamos las descargas en curso de Transmission a esta unidad
$ mkdir /mnt/lvm_ssd/torrents
$ transmission-remote -c /mnt/lvm_ssd/torrents
```
La única pega que le estoy viendo, por ahora, es lo que una vez descargado algo tarda en pasar al HDD (obviamente), pero me gusta la flexibilidad que ofrece LVM.
*NOTA: Actualización 2024:* Dejé de usar esto hace 1 año, funcionó bien pero terminé dejando de usarlo en favor de [mergerfs](https://github.com/trapexit/mergerfs) + [SnapRAID](https://www.snapraid.it/), en un intento de emular lo que hace [unraid](https://unraid.net/) como tengo en el NAS y va PERFECTO.

View File

@ -0,0 +1,45 @@
---
layout: post
title: "Aceleración de hardware (Quick Sync de Intel) en docker con Alpine"
tags: [docker, alpine, quicksync, qsv, va, ffmpeg, intel]
---
Si quieres usar Aceleración de Hardware con procesadores Intel es necesario que tengan soporte para [Quick Sync](https://en.wikipedia.org/wiki/Intel_Quick_Sync_Video) y gráfica integrada (iGPU). Normalmente suelen ser los que no tienen una `F` al final del modelo.
En mi caso tengo un servidor con [Plex](https://plex.tv/) y a veces hace Transcode de lo que se visualiza. Por ejemplo si el dispositivo es viejo (no tiene soporte para el formato del video original), la conexión va lenta, o el equipo es lento y no puede reproducir bien el formato original (resolución o codec). En todos estos casos el servidor ofrece al reproductor una versón más _light_.
El problema viene que la diferencia entre usar o no Aceleración de Hardware es brutal. Con ella el procesador no pasa de un 5% de uso, sin ella se pone a más de 90%, y si hay dos clientes que requieren Transcode ya no da de si. Necesitaría una gráfica dedicada, con el consiguiente consumo/calor/ruido extra. De ahí el interés en usar las capacidades del procesador.
PERO, si quieres usar esto desde dentro de un contenedor de Docker... se complica levemente. Tengo ciertos scripts que hacen uso de [ffmpeg](https://ffmpeg.org/) para preprocesar el contenido cuando se añaden a las bibliotecas. Tanto el contenedor ccmo lo que lleva dentro tienen que enterarse del soporte de Quick Sync. Este es un modelo de imágen con [Alpine Linux](https://www.alpinelinux.org/), Python y ffmpeg:
```Dockerfile
FROM python:alpine
WORKDIR /app
RUN apk add --no-cache ffmpeg mediainfo intel-media-driver
RUN apk add --no-cache --virtual .build-deps build-base linux-headers musl-dev python3-dev \
&& pip install --no-cache-dir -r requirements.txt \
&& apk del .build-deps
COPY *.py ./
CMD [ "python3", "main.py" ]
```
Faltan muchas cosas por medio pero el modelo está claro. La clave aquí es el paquete [`intel-media-driver`](https://pkgs.alpinelinux.org/package/edge/community/x86_64/intel-media-driver), que pertenece al repositorio [community](https://wiki.alpinelinux.org/wiki/Repositories#Community).
Ahora para hacer uso de ello bastaría con algo como:
```shell
$ docker build -t alpine_hwa .
$ docker run -it --rm --device /dev/dri:/dev/dri alpine_hwa ffmpeg -hwaccel auto -i ...
```
Se contruye el contenedor y se le da uso. Dos puntos clave:
- `--device /dev/dri:/dev/dri`: Damos visión al contenedor de la "gráfica integrada".
- `-hwaccel auto`: le decimos a `ffmpeg` que use HWA, podríamos decirle directamente que use `vaapi`, pero en este caso es lo mismo, lo derectará solo.
En el caso de tener una tarjeta gráfica NVIDIA en vez de la iGPU del procesador el proceso cambia (más sencillo), pero no lo cubro en este post porque no tengo forma de hacer pruebas.

View File

@ -0,0 +1,65 @@
---
layout: post
title: "Unraid: servidor NAS en casa"
tags: [unraid, nas, server, home, linux, raid]
---
[Unraid](https://unraid.net) es una distribución de Linux para montar una NAS. Además podrás poner en marcha VMs y contenedores de Docker, instalar apps, o usarlo como una distribución más (con sus matices).
La característica principal de este SO es la forma que tiene de gestionar los discos. Tiene algo muy similar a un [MergerFS](https://github.com/trapexit/mergerfs) + [SnapRAID](https://www.snapraid.it/), que no es más que la unión de varios discos con uno para [paridad](https://en.wikipedia.org/wiki/Parity_drive). Y no solo eso, sino que al NO SER UN RAID puedes ver el contenido de los discos desde cualquier otro equipo. Quiero decir, que quitas un disco del equipo, lo pinchas en otro, y ves el contenido qeu ha caido en ese disco concreto. Podrás si quieres copiarlo, o borrar lo que tiene, reemplazarlo por otro igual o mayor (sin superar el disco de paridad), etc. Casi casi es un [JBOD](https://en.wikipedia.org/wiki/Non-RAID_drive_architectures#JBOD) "seguro".
La diferencia con [SHR de Synology](https://kb.synology.com/en-eu/DSM/tutorial/What_is_Synology_Hybrid_RAID_SHR) es que en Unraid se usa [FUSE](https://github.com/libfuse/libfuse) por encima de los discos, y SHR usa [LVM](https://en.wikipedia.org/wiki/Logical_Volume_Manager_(Linux)). Eso da un poco más de performance, pero se pierde la "ventaja" de no usar RAID (si es que queremos verlo como ventaja).
Ok, basta de introducciones y a ver qué cacharro he montado. Este cacharro es la evolución durante años de tener discos por USB con Raspis, NUCs, y ahora ya un sistema hecho y derecho con los discos conectados por SATA. Más seguro, más versátil, y más rápido (y caro, si, pero lo compensa). Podría haber usado LSI/SAS pero para uso doméstico no me he atrevido.
# Hardware
Unraid lo tengo instalado en un *Kingston DataTraveler SE9* de 32GB. Es USB2, como recomiendan. Unraid se carga en memoria, no se instala en disco.
Para la caja uso una *Fractal Design Node 804*. Es cuadrada y con dos "cámaras", una para los componentes y otra para los discos. De lo mejorcito que he probado en mucho tiempo.
La placa base es una *MSI B560M PRO*. Si, una placa de escritorio, lo he preferido sobre la típica Supermicro por un tema de precio + disponibilidad + futura reutilización. Además tiene red de 2.5Gbit y buenas VRMs de alimentación. Chipset B560 para socket LGA1200.
Procesador *Intel Core i3-10105* de 10th generación. Buen precio, potente, "bajo" voltaje y soporte para Quick Sync, necesario para hacer Transcode con Plex, por ejemplo.
Memoria RAM *Kingston FURY Beast DDR4 2666MHz 8GB CL16*. Nada del otro mundo, no es ECC.
Fuente de alimentación *EVGA 500 GD v2 500W 80+ Gold*. Una 80+ Gold "barata", aquí seguramente haya mejores opciones.
Ventilación de CPU *NOX HUMMER H-212*. Silencioso y enfría muy muy bien, muchísimo mejor que el ventilador de stock. Y barato.
Tarjeta de expansión *PCIe SATA3 x1 6ports*. Puesto que tengo 10 discos SATA, necesito más tomas que las que trae la placa.
Tema discos, actualmente esta es la configuración de inicio para aprovechar los discos que ya tengo:
- Disco de paridad: Toshiba N300 4TB 7200RPM
- Discos de datos:
- 5x 4TB Seagate IronWolf 5900RPM
- 2x 3TB Seagate IronWolf 5200RPM
- 2x 1TB WD Red 5200RPM
- Discos de cache:
- appdata y system: Crucial MX500 250GB
- descargas: Crucial MX500 1TB
Por último, un *HDMI Dummy Plug* para que no se desactive la iGPU del procesador.
# Software
Como ya he dicho, he empezado con *Unraid*, versión 6.11.0.
Plugins esenciales:
- Auto Turbo Write Mode: permite duplicar la escritura de paridad cuando todos los discos están encendidos. Sobre todo apra mover los datos inicialmente, esto es esencial.
- Fix Common Problems: el nombre lo dice. Te avisa de cosas que no van bien.
- My Servers: Cloud de unraid, sincroniza la key.
- Unassigned Devices: aumenta las opciones de la pantalla _Main_.
- User Scripts: yo lo uso para crear crones.
Contenedores principales:
- nginx
- iperf3
- qBittorrent
- Plex
VM de momento no tengo ninguna, ni creo que vaya a usar.

View File

@ -0,0 +1,35 @@
---
layout: post
title: "Migrar almacenamiento de Docker y WSL de disco"
tags: [migrate, docker, wsl, data, windows, disk]
---
Docker en Windows usa un disco virtual para guardar su información de imágenes, por ejemplo. Este "disco" crece y lo suyo es sacarlo de `C:`. El procedimiento es el mismo para mover una imágen de Linux sobre WSL, ya que básicamente usan el mismo subsistema. Por ejemplo, para mover estos datos a un disco `M:`:
```shell
cd m:
wsl --shutdown
wsl --export docker-desktop-data docker-desktop-data.tar
wsl --unregister docker-desktop-data
wsl --import docker-desktop-data docker-desktop-data docker-desktop-data.tar --version 2
```
Y para migrar por ejemnplo una imágen de Ubuntu:
```shell
cd m:
wsl --shutdown
wsl --export Ubuntu ubuntu.tar
wsl --unregister Ubuntu
wsl --import Ubuntu Ubuntu ubuntu.tar
```
El `export` e `import` tardan un poquito, en mi caso eran unos cuantos gigas, e incluso usando un m.2 ha tardado un poquillo en ejecutarlos.
Por último, en caso de migrar Ubuntu, he tenido que volver a configurar el usuario por defecto:
```shell
ubuntu config --default-user <username>
```
Sino entrará como `root` en vez de nuestro usuario.

View File

@ -0,0 +1,107 @@
---
layout: post
title: "Abrir la LAN con WireGuard"
tags: [unraid, nas, server, home, linux, lan, wireguard, vpn]
---
Hasta ahora lo típico para acceder a la LAN de casa era abrir el puerto 22 por ejemplo, y de ahí ya acceder a los equipos de la red. Pero he ido cambiando eso por WireGuard.
Dejando a un lado las ventajas de WireGuard frente a otras VPN, las dos mayores ventajas que me han hecho cambiar a este setup son:
- Velocidad de reconexión: cuando cambias de red (por ejemplo el móvil entre antenas), o el portátil entre trabajo/casa, el propio túnel se actualiza él solo al vuelo.
- Menos consumo de red: WireGuard usa menos red, lo que se traduce en menos ping y más tasa de red.
Mi configuración inicial consistía en todos los clientes/_peers_ conectados al servidor VPN que monté en el router. Funcionaba, pero eso me hacía depender de un router con kernel moderno. Por diversas razones ya no puedo tener esa dependencia, además no quiero depende tampoco de tener que abrir puertos. Y para finalizar, me he dado cuenta ahora que el router me limitaba levemente la conexión de la VPN por la velocidad del procesador.
Por eso he pasado a tener la VPN en un VPS externo, y para acceder a la LAN de casa (u otra) lo hago _colándome_ por el túnel, concretamente por el _peer_ del NAS.
Para este setup he recurrido a diferentes artículos de referencia sobre WireGuard. Lo que más me ha ayudado, de lejos, es la [página oficial](https://www.wireguard.com/) y este comentario:
> In other words, when sending packets, the list of allowed IPs behaves as a sort of routing table, and when receiving packets, the list of allowed IPs behaves as a sort of access control list.
Otra página que me ha servido de infinita ayuda es [pirate/wireguard-docs](https://github.com/pirate/wireguard-docs). No solo explica cada caso de uso y parámetro, sino que detalla las tripas de WireGuard.
Entre algunos de los problemas que me salieron fue que uno de los _peers_ tenía baja velocidad de transferencia. Resultó ser por el MTU cuando el _endpoint_ iba por IPv6, explicado [aquí](https://keremerkan.net/posts/wireguard-mtu-fixes/).
Y para acabar, los [tutoriales en el foro de Unraid sobre WireGuard](https://forums.unraid.net/topic/84226-wireguard-quickstart/), sobre todo el de [LAN-to-LAN](https://forums.unraid.net/topic/88906-lan-to-lan-wireguard/), donde vi la idea de crear una ruta estática en casa, algo que soportan todos los routers hoy en día.
El resto es cosa de los comandos `ip` e `iptables` de linux .
# Configuración modelo
Servidor:
```
[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
MTU = 1412
PrivateKey = ...
PostUp = iptables -A FORWARD -i %i -j ACCEPT
PostUp = iptables -A FORWARD -o %i -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o %i -j MASQUERADE
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PreDown = iptables -D FORWARD -i %i -j ACCEPT
PreDown = iptables -D FORWARD -o %i -j ACCEPT
PreDown = iptables -t nat -D POSTROUTING -o %i -j MASQUERADE
PreDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
[Peer]
# Name = nas
PublicKey = ...
AllowedIPs = 10.0.0.2/32, 192.168.1.0/24
```
Con esto hacemos que cualquier conexión que nos venga para la LAN de casa (192.168.1.0/24) se mande por el _peer_ 10.0.0.2 (el nas).
Ahora la config del nas:
```
[Interface]
PrivateKey = ...
Address = 10.0.0.2/24
ListenPort = 51820
MTU = 1412
PostUp = iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o br0 -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -s 10.0.0.0/24 -o br0 -j MASQUERADE
PostUp = ip -4 route flush table 200
PostUp = ip -4 route add default via 10.0.0.2 table 200
PostUp = ip -4 route add 192.168.1.0/24 via 192.168.1.1 table 200
PostDown = ip -4 route flush table 200
PostDown = ip -4 route add unreachable default table 200
PostDown = ip -4 route add 192.168.1.0/24 via 192.168.1.1 table 200
[Peer]
# Name = vps
PublicKey = ...
Endpoint = 1.2.3.4:51820
AllowedIPs = 10.0.0.0/24, 172.20.0.0/24
PersistentKeepalive = 25
```
Con este `AllowedIPs` hacemos que se vaya por el túnel todo lo de la VPN y lo de la red de los Dockers que hay en el VPS. Para que cualquier equipo conectado al router de casa se le aplica la ruta estática configurada en el mismo, que precisamente manda estos dos rangos a la IP local del nas, dicho de otra forma, 172.20.0.0/24 redireccionado al "gateway" 192.168.1.2 . Y las rutas y reglas de `iptables` sirven precisamente para que cualquiera que venga por el túnel a otro equipo de la LAN, pueda acceder.
El resto de _peers_ de la VPN no necesitan una config especial, por ejemplo al móvil o al portátil les vale con algo así para acceder tanto a la LAN como a los Dockers:
```
[Interface]
PrivateKey = ...
Address = 10.0.0.x/24
ListenPort = 51820
[Peer]
# Name = movil
PublicKey = ...
Endpoint = 1.2.3.4:51820
AllowedIPs = 192.168.1.0/24, 172.20.0.0/24
```
Resumiendo:
- Todos los _peers_ de la VPN pueden hablar entre si, con los contenedores de Docker del VPS, y con los equipos de la LAN de casa (incluido el NAS y el router).
- El VPS y sus contenedores pueden acceder a la LAN y a los _peers_.
- Cualquier equipo de casa, NAS y router incluidos, pueden acceder a los contendores del VPS y al resto de _peers_.
Total, que todos pueden conectar a todos solo por estar a rango de la VPN.
Y lo mejor de este setup es que me puedo llevar el NAS a otra conexión/casa, o cambiar de proveedor/equipos/router, y con un mínimo ajuste (rutas estáticas) todo funcionaría igual.

View File

@ -0,0 +1,15 @@
---
title: "Simplificando el blog, aun más"
---
El otro día fui a escribir un post y resulta que [Jekyll](https://jekyllrb.com/) no montaba el sitio por no séquépaquete desactualizado por la versión de ruby nosequé. Excusa perfecta para deshacerme de ello y simplificar más aun esto.
Como prácticamente esto son 4 `.md` mal contados, por poco pensé "mira, los publico tal cual", pero con una búsqueda rápida descubrí [pandoc](https://pandoc.org/), un simple conversor, entre ellos de `.md` a `.html`. PERFECTO.
Así que he creado [un script básico](https://sergio.am/code/sergio.am/src/branch/main/build.sh) para pasar todos los Markdown a HTML, y he cambiado el [CI/CD](https://sergio.am/code/sergio.am/src/branch/main/.drone.yml).
Además, para no cambiar nada más, he seguido el formato de Jekyll en la medida de lo posible.
Igual a futuro añado más cosas, pero algo me dice que de hecho quitaré algo. Tags? _pa qué_, RSS? Tal vez... (alguien lo usa???), comentario? nah... que me manden [un mail](/about.html). Y ojo que igual hasta quito el CSS, es lo que más tiempo me ha llevado y casi casi me lo cepillo.
En fin, por eso, que más sencillo aun.

View File

@ -1,377 +0,0 @@
// Reset some basic elements
* {
-webkit-transition: background-color 75ms ease-in, border-color 75ms ease-in;
-moz-transition: background-color 75ms ease-in, border-color 75ms ease-in;
-ms-transition: background-color 75ms ease-in, border-color 75ms ease-in;
-o-transition: background-color 75ms ease-in, border-color 75ms ease-in;
transition: background-color 75ms ease-in, border-color 75ms ease-in;
}
.notransition {
-webkit-transition: none;
-moz-transition: none;
-ms-transition: none;
-o-transition: none;
transition: none;
}
html {
overflow-x: hidden;
width: 100%;
}
body,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
hr,
dl,
dd,
ol,
ul,
figure {
margin: 0;
padding: 0;
}
// Basic styling
body {
min-height: 100vh;
overflow-x: hidden;
position: relative;
color: $text-base-color;
font: $normal-weight #{$base-font-size}/#{$base-line-height} $sans-family;
-webkit-text-size-adjust: 100%;
-webkit-font-smoothing: antialiased;
-webkit-font-feature-settings: "kern" 1;
-moz-font-feature-settings: "kern" 1;
-o-font-feature-settings: "kern" 1;
font-feature-settings: "kern" 1;
font-kerning: normal;
box-sizing: border-box;
background: url(/assets/img/simple_dashed_l.png);
background-color: $white;
background-repeat: repeat;
background-attachment: fixed;
}
// Set `margin-bottom` to maintain vertical rhythm
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
ul,
ol,
dl,
figure,
%vertical-rhythm {
margin-top: $spacing-full - 20;
margin-bottom: $spacing-full - 20;
}
// strong | bold
strong,
b {
font-weight: $bold-weight;
color: $black;
}
// horizontal rule
hr {
border-bottom: 0;
border-style: solid;
border-color: $light;
}
// kbd tag
kbd {
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
border: 1px solid $light;
border-radius: 2px;
color: $black;
display: inline-block;
font-size: $small-font-size;
line-height: 1.4;
font-family: $mono-family;
margin: 0 0.1em;
font-weight: $bold-weight;
padding: 0.01em 0.4em;
text-shadow: 0 1px 0 $white;
}
// Image
img {
max-width: 100%;
vertical-align: middle;
-webkit-user-drag: none;
margin: 0 auto;
text-align: center;
}
// Figure
figure {
position: relative;
}
// Image inside Figure tag
figure > img {
display: block;
position: relative;
}
// Image caption
figcaption {
font-size: 13px;
text-align: center;
}
// List
ul {
list-style: none;
li {
display: list-item;
text-align: -webkit-match-parent;
}
li::before {
content: "\FE63";
display: inline-block;
top: -1px;
width: 1.2em;
position: relative;
margin-left: -1.3em;
font-weight: 700;
}
}
ol {
list-style: none;
counter-reset: li;
li {
position: relative;
counter-increment: li;
&::before {
content: counter(li);
display: inline-block;
width: 1em;
margin-right: 0.5em;
margin-left: -1.6em;
text-align: right;
direction: rtl;
font-weight: $bold-weight;
font-size: $small-font-size;
}
}
}
ul,
ol {
margin-top: 0;
margin-left: $spacing-full;
}
li {
padding-bottom: 1px;
padding-top: 1px;
&:before {
color: $black;
}
> ul,
> ol {
margin-bottom: 2px;
margin-top: 0;
}
}
// Headings
h1,
h2,
h3,
h4,
h5,
h6 {
color: $black;
font-weight: $bold-weight;
& + ul,
& + ol {
margin-top: 10px;
}
@include media-query($on-mobile) {
scroll-margin-top: 65px;
}
}
// Headings with link
h1 > a,
h2 > a,
h3 > a,
h4 > a,
h5 > a,
h6 > a {
text-decoration: none;
border: none;
&:hover {
text-decoration: none;
border: none;
}
}
// Link
a {
color: inherit;
text-decoration: none;
&:hover {
color: $text-link-blue;
}
&:focus {
outline: 3px solid rgba(0, 54, 199, 0.6);
outline-offset: 2px;
}
}
// Del
del {
color: inherit;
}
// Em
em {
color: inherit;
}
// Blockquotes
blockquote {
color: $gray;
font-style: italic;
text-align: center;
opacity: 0.9;
border-top: 1px solid $light;
border-bottom: 1px solid $light;
padding: 10px;
margin-left: 10px;
margin-right: 10px;
font-size: 1em;
> :last-child {
margin-bottom: 0;
margin-top: 0;
}
}
// Wrapper
.wrapper {
max-width: -webkit-calc(#{$narrow-size} - (#{$spacing-full} * 2));
max-width: calc(#{$narrow-size} - (#{$spacing-full} * 2));
position: relative;
margin-right: auto;
margin-left: auto;
padding-right: $spacing-full;
padding-left: $spacing-full;
padding-top: 1px;
background-color: #dddddd70;
@extend %clearfix;
@include media-query($on-mobile) {
max-width: -webkit-calc(#{$narrow-size} - (#{$spacing-full}));
max-width: calc(#{$narrow-size} - (#{$spacing-full}));
padding-right: $spacing-full - 10;
padding-left: $spacing-full - 10;
&.blurry {
animation: 0.2s ease-in forwards blur;
-webkit-animation: 0.2s ease-in forwards blur;
}
}
.header-title {
font-weight: 700;
}
}
// Underline
u {
text-decoration-color: #d2c7c7;
}
// Small
small {
font-size: $small-font-size;
}
// Superscript
sup {
border-radius: 10%;
top: -3px;
left: 2px;
font-size: small;
position: relative;
margin-right: 2px;
}
// Table
.overflow-table {
overflow-x: auto;
}
table {
width: 100%;
margin-top: $spacing-half;
border-collapse: collapse;
font-size: $small-font-size;
thead {
font-weight: $bold-weight;
color: $black;
border-bottom: 1px solid $light;
}
th,
td,
tr {
border: 1px solid $light;
padding: 2px 7px;
}
}
// Clearfix
%clearfix:after {
content: "";
display: table;
clear: both;
}
// When mouse block a text set this color
mark,
::selection {
background: #fffba0;
color: $black;
}
// Github Gist clear border
.gist {
table {
border: 0;
tr,
td {
border: 0;
}
}
}

View File

@ -1,254 +0,0 @@
body[data-theme="dark"] {
color: $dark-text-base-color;
background-color: $dark-black;
background-image: url(/assets/img/simple_dashed.png);
// Heading
h1,
h2,
h3,
h4,
h5,
h6 {
color: $dark-white;
}
// Table
table {
thead {
color: $dark-white;
border-color: $dark-light;
}
th,
td,
tr {
border-color: $dark-light;
}
}
.wrapper {
background-color: #1c1c1c70;
}
// Post
.page-content {
a {
color: $dark-text-link-blue;
&:hover,
&:active,
&:focus {
color: $dark-text-link-blue-active;
}
}
h3 {
border-color: $dark-light;
}
h1,
h2,
h3,
h4,
h5,
h6 {
.anchor-head {
color: $dark-text-link-blue;
}
}
}
// Syntax
code {
&.highlighter-rouge {
background-color: $dark-light;
}
}
// kbd tag
kbd {
border-color: $dark-light;
color: $dark-white;
text-shadow: 0 1px 0 $dark-black;
}
// horizontal rule
hr {
border-color: $dark-light;
}
// Post Meta
.post-meta {
color: $dark-gray;
time {
&::after {
background-color: $dark-light;
}
}
span[itemprop="author"] {
border-color: $dark-light;
}
}
// Link
a {
color: inherit;
text-decoration-color: $dark-smoke;
&:hover {
color: $dark-text-link-blue;
}
&:focus {
outline-color: rgba(255, 82, 119, 0.6);
}
}
// List
li {
&:before {
color: $dark-white;
}
}
// Blockquote
blockquote {
color: $dark-gray;
border-color: $dark-light;
}
// Strong, Bold
strong,
b {
color: $dark-white;
}
// Navbar
.navbar {
border-color: $dark-light;
.menu {
a#mode {
.mode-sunny {
display: block;
}
.mode-moon {
display: none;
}
}
.menu-link {
color: $dark-white;
}
@include media-query($on-mobile) {
background-color: $dark-black;
border-color: $dark-light;
.menu-icon {
> svg {
fill: $dark-white;
}
}
input[type="checkbox"]:checked ~ .trigger {
background: $dark-black;
}
}
}
}
// Post Item
.post-item {
&:not(:first-child) {
border-color: $dark-light;
}
.post-item-date {
color: $dark-white;
}
.post-item-title {
a {
color: $dark-text-link-blue;
&:hover,
&focus {
color: $dark-white;
}
}
}
}
// Post Navigation
.post-nav {
border-color: $dark-light;
background: $dark-light;
background: linear-gradient(90deg, rgba(0,0,0,0) 0%, rgba(27,29,37,1) 50%, rgba(0,0,0,0) 100%);
.post-nav-item {
font-weight: $bold-weight;
.post-title {
color: $dark-white;
opacity: 0.9;
}
&:hover,
&:focus {
.post-title {
color: $dark-text-link-blue-active;
}
}
.nav-arrow {
color: $dark-gray;
}
}
@include media-query($on-mobile) {
.post-nav-item:nth-child(even) {
border-color: $dark-light;
}
}
}
// Footer
.footer {
span.footer_item {
color: $dark-white;
}
a.footer_item:not(:last-child) {
color: $dark-white;
}
.footer_copyright {
color: $dark-gray;
opacity: 1;
}
}
// 404 Page
.not-found {
.title {
color: $dark-white;
text-shadow: 1px 0px 0px $dark-text-link-blue;
}
.phrase {
color: $dark-text-base-color;
}
.solution {
color: $dark-text-link-blue;
}
.solution:hover {
color: $dark-text-link-blue-active;
}
}
.search-article {
input[type="search"] {
color: $dark-text-base-color;
&::-webkit-input-placeholder {
color: rgba(128,128,128,0.8);
}
}
}
}

View File

@ -1,188 +0,0 @@
@charset "utf-8";
/*
* https://fonts.googleapis.com/css2?family=JetBrains+Mono&family=Roboto:wght@400;700&display=swap
* https://fonts.google.com/specimen/Roboto
* https://fonts.google.com/specimen/jetBrains+Mono
*/
/* cyrillic-ext */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/jetbrainsmono/v12/tDbY2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKxTN1OVgaY.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/jetbrainsmono/v12/tDbY2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKxTPlOVgaY.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/jetbrainsmono/v12/tDbY2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKxTOVOVgaY.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/jetbrainsmono/v12/tDbY2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKxTNVOVgaY.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/jetbrainsmono/v12/tDbY2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKxTNFOVgaY.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/jetbrainsmono/v12/tDbY2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKxTOlOV.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/roboto/v29/KFOmCnqEu92Fr1Mu72xKOzY.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/roboto/v29/KFOmCnqEu92Fr1Mu5mxKOzY.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/roboto/v29/KFOmCnqEu92Fr1Mu7mxKOzY.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/roboto/v29/KFOmCnqEu92Fr1Mu4WxKOzY.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/roboto/v29/KFOmCnqEu92Fr1Mu7WxKOzY.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/roboto/v29/KFOmCnqEu92Fr1Mu7GxKOzY.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/roboto/v29/KFOmCnqEu92Fr1Mu4mxK.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmWUlfCRc4EsA.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmWUlfABc4EsA.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmWUlfCBc4EsA.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmWUlfBxc4EsA.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmWUlfCxc4EsA.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmWUlfChc4EsA.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmWUlfBBc4.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

View File

@ -1,379 +0,0 @@
// Navbar
.navbar {
height: auto;
max-width: calc(#{$wide-size} - (#{$spacing-full} * 2));
max-width: -webkit-calc(#{$wide-size} - (#{$spacing-full} * 2));
position: relative;
margin-right: auto;
margin-left: auto;
border-bottom: 1px solid $light;
padding: $spacing-full - 15px $spacing-full;
@extend %clearfix;
}
// Navigation
.menu {
user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
a#mode {
float: right;
right: 8px;
top: 6px;
position: relative;
-webkit-transform: scale(1, 1);
transform: scale(1, 1);
opacity: 0.7;
z-index: 1;
&:hover {
cursor: pointer;
opacity: 1;
}
&:active {
-webkit-transform: scale(0.9, 0.9);
transform: scale(0.9, 0.9);
}
.mode-moon {
display: block;
line {
stroke: $black;
fill: none;
}
circle {
fill: $black;
stroke: $black;
}
}
.mode-sunny {
display: none;
line {
stroke: $dark-white;
fill: none;
}
circle {
fill: none;
stroke: $dark-white;
}
}
}
.trigger {
float: left;
}
.menu-trigger {
display: none;
}
.menu-icon {
display: none;
}
.menu-link {
color: $black;
line-height: $base-line-height + 0.4;
text-decoration: none;
padding: 5px 8px;
opacity: 0.7;
letter-spacing: 0.3px;
&:hover {
opacity: 1;
}
&:not(:last-child) {
margin-right: 5px;
}
&.rss {
position: relative;
bottom: -3px;
outline: none;
}
@include media-query($on-mobile) {
opacity: 0.8;
}
}
.menu-link.active {
opacity: 1;
font-weight: 600;
}
@include media-query($on-mobile) {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 2;
text-align: center;
height: 50px;
background-color: $white;
border-bottom: 1px solid $light;
a#mode {
right: 20px;
top: 12px;
}
.menu-icon {
display: block;
position: absolute;
left: 10px;
width: 50px;
height: 23px;
line-height: 0;
padding-top: 13px;
padding-bottom: 15px;
cursor: pointer;
text-align: center;
z-index: 1;
> svg {
fill: $black;
opacity: 0.7;
}
&:hover {
> svg {
opacity: 1;
}
}
&:active {
-webkit-transform: scale(0.9, 0.9);
transform: scale(0.9, 0.9);
}
}
input[type="checkbox"]:not(:checked) ~ .trigger {
clear: both;
visibility: hidden;
}
input[type="checkbox"]:checked ~ .trigger {
position: fixed;
animation: 0.2s ease-in forwards fadein;
-webkit-animation: 0.2s ease-in forwards fadein;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
background-color: $white;
height: 100vh;
width: 100%;
top: 0;
}
.menu-link {
display: block;
box-sizing: border-box;
font-size: 1.1em;
&:not(:last-child) {
margin: 0;
padding: 2px 0;
}
}
}
}
// Author
.author {
margin-top: 4rem;
margin-bottom: 4rem;
text-align: center;
@include media-query($on-mobile) {
margin-bottom: 3em;
}
.author-avatar {
width: 70px;
height: 70px;
border-radius: 100%;
user-select: none;
background-color: $black;
-ms-user-select: none;
-webkit-user-select: none;
-webkit-animation: 0.5s ease-in forwards fadein;
animation: 0.5s ease-in forwards fadein;
opacity: 1;
}
.author-name {
font-size: 1.7em;
margin-bottom: 2px;
}
.author-bio {
margin: 0 auto;
opacity: 0.9;
max-width: 393px;
line-height: 1.688;
}
}
// Content
.posts-item-note {
font-size: $base-font-size;
font-weight: 700;
margin-bottom: 5px;
color: $black;
}
// List of posts
.post-item {
display: flex;
padding-top: 5px;
padding-bottom: 6px;
@extend %clearfix;
&:not(:first-child) {
border-top: 1px solid $light;
}
.post-item-date {
min-width: 96px;
color: $black;
font-weight: 700;
padding-right: 10px;
@include media-query($on-mobile) {
font-size: 16px;
}
}
.post-item-title {
margin: 0;
border: 0;
padding: 0;
font-size: $base-font-size;
font-weight: normal;
letter-spacing: 0.1px;
a {
color: $text-link-blue;
&:hover,
&focus {
color: $black;
}
}
}
}
// Footer
.footer {
margin-top: 2em;
margin-bottom: 2em;
text-align: center;
@include media-query($on-mobile) {
margin-top: 3em;
}
span.footer_item {
color: $black;
opacity: 0.8;
font-weight: $bold-weight;
font-size: $small-font-size;
}
a.footer_item {
color: $black;
opacity: 0.8;
text-decoration: none;
&:not(:last-child) {
margin-right: 10px;
&:hover {
opacity: 1;
}
}
}
.footer_copyright {
font-size: $small-font-size - 1;
margin-top: 3px;
display: block;
color: $gray;
opacity: 0.8;
}
}
.not-found {
text-align: center;
display: flex;
justify-content: center;
flex-direction: column;
height: 75vh;
.title {
font-size: 5em;
font-weight: $bold-weight;
line-height: 1.1;
color: $black;
text-shadow: 1px 0px 0px $text-link-blue;
}
.phrase {
color: $text-base-color;
}
.solution {
color: $text-link-blue;
letter-spacing: 0.5px;
}
.solution:hover {
color: $text-link-blue-active;
}
}
.search-article {
position: relative;
margin-bottom: 50px;
label[for="search-input"] {
position: relative;
top: 10px;
left: 11px;
}
input[type="search"] {
top: 0;
left: 0;
border: 0;
width: 100%;
height: 40px;
outline: none;
position: absolute;
border-radius: 5px;
padding: 10px 10px 10px 35px;
color: $text-base-color;
-webkit-appearance: none;
font-size: $base-font-size;
background-color: rgba(128, 128, 128, 0.1);
border: 1px solid rgba(128, 128, 128, 0.1);
&::-webkit-input-placeholder {
color: #808080;
}
&::-webkit-search-decoration,
&::-webkit-search-results-decoration {
display: none;
}
}
}
#search-results {
text-align: center;
li {
text-align: left;
}
}
.archive-tags {
height: auto;
.tag-item {
padding: 1px 3px;
border-radius: 2px;
border: 1px solid rgba(128, 128, 128, 0.1);
background-color: rgba(128, 128, 128, 0.1);
}
}

View File

@ -1,41 +0,0 @@
// Animation fade-in
@keyframes fadein {
0% {
opacity: 0.2;
}
100% {
opacity: 0.8;
}
}
// Animation blur
@keyframes blur {
0% {
filter: blur(0px);
}
100% {
filter: blur(4px);
}
}
// Responsive embed video
.embed-responsive {
height: 0;
max-width: 100%;
overflow: hidden;
position: relative;
padding-bottom: 56.25%;
margin-top: 20px;
iframe,
object,
embed {
top: 0;
left: 0;
width: 100%;
height: 100%;
position: absolute;
}
}

View File

@ -1,262 +0,0 @@
// Post wrapper
.wrapper.post {
@include media-query($on-mobile) {
padding-left: $spacing-half;
padding-right: $spacing-half;
}
}
// Post title
.header {
margin-top: 3em;
margin-bottom: 3em;
.tags {
margin-left: 3px;
letter-spacing: 0.5px;
.tag {
font-weight: $bold-weight;
font-size: $small-font-size - 2;
&:hover {
text-decoration: none;
}
}
}
.header-title {
font-size: 2em;
line-height: 1.2;
margin-top: 10px;
margin-bottom: 20px;
&.center {
text-align: center;
}
@include media-query($on-mobile) {
font-size: 1.9em;
}
}
}
// Post meta
.post-meta {
padding-top: 3px;
line-height: 1.3;
color: $gray;
time {
position: relative;
margin-right: 1.5em;
&::after {
background: $light;
bottom: 1px;
content: " ";
height: 2px;
position: absolute;
right: -20px;
width: 12px;
}
}
span[itemprop="author"] {
border-bottom: 1px dotted $light;
}
}
// Post content
.page-content {
iframe {
text-align: center;
}
figure {
img {
border-radius: 2px;
}
figcaption {
margin-top: 5px;
font-style: italic;
font-size: $small-font-size;
}
}
a {
color: $text-link-blue;
text-decoration: none;
&[target="_blank"]::after {
content: " \2197";
font-size: $small-font-size;
line-height: 0;
position: relative;
bottom: 5px;
vertical-align: baseline;
}
&:hover {
color: $text-link-blue-active;
}
&:focus {
color: $text-link-blue;
}
}
> p {
margin: 0;
padding-top: $spacing-full - 15;
padding-bottom: $spacing-full - 15;
}
ul.task-list {
list-style: none;
margin: 0;
li::before {
content: "";
}
li input[type="checkbox"] {
margin-right: 10px;
}
}
dl dt {
font-weight: $bold-weight;
}
h1,
h2,
h3,
h4,
h5,
h6 {
color: $black;
font-weight: $bold-weight;
margin-top: $spacing-full;
margin-bottom: 0;
&:hover {
.anchor-head {
color: $text-link-blue;
opacity: 1;
}
}
.anchor-head {
position: relative;
opacity: 0;
outline: none;
&::before {
content: "#";
position: absolute;
right: -3px;
width: 1em;
font-weight: $bold-weight;
}
}
}
h1 {
@include relative-font-size(1.5);
}
h2 {
@include relative-font-size(1.375);
}
h3 {
@include relative-font-size(1.25);
border-bottom: 1px solid $light;
padding-bottom: 4px;
}
h4 {
@include relative-font-size(1.25);
}
h5 {
@include relative-font-size(1);
}
h6 {
@include relative-font-size(0.875);
}
}
.post-nav {
display: flex;
position: relative;
margin-top: 3em;
border-top: 1px solid $light;
background: $light;
background: linear-gradient(90deg, rgba(0,0,0,0) 0%, rgba(236,236,236,1) 50%, rgba(0,0,0,0) 100%);
line-height: 1.4;
.post-nav-item {
border-bottom: 0;
font-weight: $bold-weight;
padding-bottom: 10px;
.post-title {
color: $black;
}
&:hover,
&:focus {
.post-title {
color: $text-link-blue-active;
opacity: 0.9;
}
}
.nav-arrow {
font-weight: $normal-weight;
font-size: $small-font-size;
color: $gray;
margin-bottom: 3px;
}
width: 50%;
padding-top: 10px;
text-decoration: none;
box-sizing: border-box;
&:nth-child(odd) {
padding-left: 0;
padding-right: 20px;
}
&:nth-child(even) {
text-align: right;
padding-right: 0;
padding-left: 20px;
}
}
@include media-query($on-mobile) {
display: block;
font-size: $small-font-size;
.post-nav-item {
display: block;
width: 100%;
}
.post-nav-item:nth-child(even) {
border-left: 0;
padding-left: 0;
border-top: 1px solid $light;
}
}
}
.post-updated-at {
font-family: "JetBrains Mono", "monospace";
}

View File

@ -1,182 +0,0 @@
// Code
code {
font-family: $mono-family;
text-rendering: optimizeLegibility;
font-feature-settings: "calt" 1;
font-variant-ligatures: normal;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
font-size: inherit;
&.highlighter-rouge {
padding: 1px 3px;
position: relative;
top: -1px;
background-color: #f6f6f6;
border-radius: 2px;
border: 1px solid rgba(128,128,128,0.1);
}
}
// Codeblock Theme
pre.highlight, pre {
margin: 0 -30px;
@include media-query($on-mobile) {
margin: 0 calc(51% - 51vw);
padding-left: 20px;
}
background-color: #1a1b21;
padding: 10px;
display: block;
overflow-x: auto;
> code {
width: 100%;
margin-left: auto;
margin-right: auto;
line-height: 1.5;
display: block;
border: 0;
}
}
.highlight table td {
padding: 5px;
}
.highlight table pre {
margin: 0;
}
.highlight,
.highlight .w {
color: #fbf1c7;
// background-color: #1a1b21;
}
.highlight .err {
color: #fb4934;
// background-color: #1a1b21;
font-weight: bold;
}
.highlight .c,
.highlight .cd,
.highlight .cm,
.highlight .c1,
.highlight .cs {
color: #928374;
font-style: italic;
}
.highlight .cp {
color: #8ec07c;
}
.highlight .nt {
color: #fb4934;
}
.highlight .o,
.highlight .ow {
color: #fbf1c7;
}
.highlight .p,
.highlight .pi {
color: #fbf1c7;
}
.highlight .gi {
color: #b8bb26;
background-color: #282828;
}
.highlight .gd {
color: #fb4934;
background-color: #282828;
}
.highlight .gh {
color: #b8bb26;
font-weight: bold;
}
.highlight .k,
.highlight .kn,
.highlight .kp,
.highlight .kr,
.highlight .kv {
color: #fb4934;
}
.highlight .kc {
color: #d3869b;
}
.highlight .kt {
color: #fabd2f;
}
.highlight .kd {
color: #fe8019;
}
.highlight .s,
.highlight .sb,
.highlight .sc,
.highlight .sd,
.highlight .s2,
.highlight .sh,
.highlight .sx,
.highlight .s1 {
color: #b8bb26;
font-style: italic;
}
.highlight .si {
color: #b8bb26;
font-style: italic;
}
.highlight .sr {
color: #b8bb26;
font-style: italic;
}
.highlight .se {
color: #fe8019;
}
.highlight .nn {
color: #8ec07c;
}
.highlight .nc {
color: #8ec07c;
}
.highlight .no {
color: #d3869b;
}
.highlight .na {
color: #b8bb26;
}
.highlight .m,
.highlight .mf,
.highlight .mh,
.highlight .mi,
.highlight .il,
.highlight .mo,
.highlight .mb,
.highlight .mx {
color: #d3869b;
}
.highlight .ss {
color: #83a598;
}

View File

@ -1,63 +0,0 @@
// Fonts preferences
$sans-family: Roboto, sans-serif;
$mono-family: JetBrains Mono, Consolas, monospace;
$base-font-size: 16px;
$medium-font-size: $base-font-size * 0.938;
$small-font-size: $base-font-size * 0.875;
$base-line-height: 1.85;
// Font weight
// $light-weight: 300; // uncomment if necessary
$normal-weight: 400;
$bold-weight: 700;
// $black-weight: 900; // uncomment if necessary
//Light Colors
$text-base-color: #434648;
$text-link-blue: #003fff;
$text-link-blue-active: #0036c7;
$black: #0d122b;
$light: #ececec;
$smoke: #d2c7c7;
$gray: #6b7886;
$white: #fff;
// Dark Colors
$dark-text-base-color: #d0d0d0;
$dark-text-link-blue: #ff5277;
$dark-text-link-blue-active: #ff2957;
$dark-black: #131418;
$dark-white: #eaeaea;
$dark-light: #1b1d25;
$dark-smoke: #4a4d56;
$dark-gray: #767f87;
// Width of the content area
$wide-size: 890px;
$narrow-size: 890px;
// Padding unit
$spacing-full: 30px;
$spacing-half: $spacing-full / 2;
// State of devices
$on-mobile: 768px;
$on-tablet: 769px;
$on-desktop: 1024px;
$on-widescreen: 1152px;
@mixin media-query($device) {
@media screen and (max-width: $device) {
@content;
}
}
@mixin relative-font-size($ratio) {
font-size: $base-font-size * $ratio;
}
// Import sass files
@import "klise/fonts", "klise/base", "klise/layout", "klise/post",
"klise/miscellaneous", "klise/syntax", "klise/dark";

View File

@ -1,19 +1,24 @@
--- ---
title: Qué es esto title: Qué es esto?
permalink: /about/ permalink: /about/
layout: page layout: page
comments: false comments: false
--- ---
## Yo Esto podría resumirse en un cementerio de textos que de vez en cuando (de año en año) actualizo, abandono, actualizo, abandono, etc.
Técnicamente soy un simple programador, desde los 16 años que me compraron mi primer ordenador y empecé a aprender todo por mi cuenta. Tengo la mente demasiado dispersa y lo que aquí se publica es un ejemplo de ello. No es la primera vez que monto algo así, ni será la última. Por eso, para mi yo del futuro, voy a dejar unas premisas, unos _por qués_, y unos _cómos_ para no reinventar la rueda ni recorrer caminos ya andados (y fallidos).
## Este sitio ## Contacto
No es la primera vez que monto algo así, ni será la última. Por eso, para mi yo del futuro, voy a dejar unas premisas, unos por qués, y unos cómos para no reinventar la rueda ni recorrer caminos ya andados. Lo primero, cómo puedes encontrarme:
### Premisas - [correo@sergio.am](mailto:correo@sergio.am?subject=sergio.am), es lo más cómodo, el tradicional email.
- [Telegram](https://t.me/sxergio), ahí seguro que también lo veo.
Así de simple, cualquier otro método seguramente pierda el mensaje.
## Premisas
Este sitio tiene como objetivo: Este sitio tiene como objetivo:
@ -25,25 +30,23 @@ Este sitio tiene como objetivo:
Y todo ello tiene que ser: Y todo ello tiene que ser:
- sencillo. [KISS](https://en.wikipedia.org/wiki/KISS_principle) - sencillo
- mantenible. Obvio... - mantenible
- portable. Alguna vez se os ha petado un disco/servidor? Pues eso. - portable
- autosuficiente. Mi sueño es que, si me muero, esto quede para siempre disponible aunque no haya nadie al mando. - autosuficiente
### Por qué ## Por qué
Eso, por qué otra vez esto, o por qué otro sitio como los miles que ya habrá por internet. Ok, sencillo... porque me da pereza tener que buscar en google una y otra vez lo mismo pasado X tiempo. Porque a veces me baso en otras ideas, tutoriales, documentos, etc. y lo que termino haciendo yo no tiene una explicación exacta, o una línea recta que seguir, y necesito apuntarme mis cambios o cómo lo he hecho. He tenido muchos por qué, pero a día de hoy es simplemente por recordar a futuro cómo he hecho alguna cosa. Todo lo que hago suele ser un mix de cosas que busco, aprendo y experimento, y cuando pasa el tiempo se me olvida el camino recorrido.
Y porque mi memoria es muy corta y limitada, me quedo con chorradas pero no con lo importante. Y esto, para mi, es importante. Así que a veces necesito un rinconcito donde anotarme cosas, y si encima sirven para que otra persona, mejor. Al igual que me han servidor a mi los textos de otros, es una pqueña forma de devolver el favor.
### Cómo ## Cómo
Habrá otros documentos para explicar toda la plataforma que tengo montada. Pero este sitio en concreto no. Ya tengo una edad donde no me apetece recordar cómo he montado las cosas básicas, por lo cada vez lo simplifico y recorto más.
Genero el sitio de forma estática con [Jekyll](https://jekyllrb.com/) por conveniencia y lo extendido que está. Además no es la primera vez que lo uso y minimizaba mi apredizaje. Uso el theme [Klisé](http://github.com/piharpi/jekyll-klise) modificado. Actualmente es tan sencillo como escribir unos `.md` y con un script muy sencillo los convierto a `.html` con [pandoc](https://pandoc.org/). YA ESTÁ.
Edito los `.md` via terminal, [Sublime](https://www.sublimetext.com/) o el editor de [Gitea](https://gitea.io/). Según me de. Lo textos los edito via terminal, o con el editor de turno, o directamente desde [Gitea](https://github.com/go-gitea/gitea). Según me de o dónde me pille. En el fondo es un repo de Git con un puñado de archivos. Con un simple `post-hook` reconstruyo el sitio y ya.
Actualmente uso la conexión de casa para servir el sitio pero la idea es que no haga falta, por ello estoy pensando entre usar el [Free Tier de AWS](https://aws.amazon.com/free/) o sitios gratuitos como [Netlify](https://netlify.com)+[Cloudflare](https://cloudflare.com), [Github Pages](https://pages.github.com/), o similares. Y poco más, todo esto puedo luego publicarlo desde un simple nginx, sitios como GitHub Pages o Cloudflare, una RasPi, da igual. Son archivos estáticos que se sirven hasta por señales de humo :)
Para los DNS y dominios uso [OVH](https://ovh.com). No todos tienen `.io` y `.am`...

View File

@ -1,29 +0,0 @@
---
title: Archive
permalink: /archive/
layout: page
excerpt: All post.
comments: false
---
<div class="search-article">
<label for="search-input" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="rgba(128,128,128,0.8)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
</label>
<input type="search" id="search-input" placeholder="Find some articles here" aria-label="Search">
</div>
<ul id="search-results"></ul>
{%- for post in site.posts -%}
{%- capture current_year -%}{{ post.date | date: "%Y" }}{%- endcapture -%}
{%- unless current_year == previous_year -%}
<h2>{{ current_year }}</h2>
{%- assign previous_year = current_year -%}
{%- endunless -%}
<article class="post-item">
<h3 class="post-item-title">
<a href="{{ post.url }}">{{ post.title | escape }}</a>
</h3>
</article>
{%- endfor -%}

View File

@ -1,4 +0,0 @@
---
---
@import "main";

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 914 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1 +0,0 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 537 B

View File

@ -1 +0,0 @@
!function(t,e,n){"use strict";var o=function(t,e){var n,o;return function(){var i=this,r=arguments,d=+new Date;n&&d<n+t?(clearTimeout(o),o=setTimeout(function(){n=d,e.apply(i,r)},t)):(n=d,e.apply(i,r))}},i=!1,r=!1,d=!1,s=!1,a="unloaded",u=!1,l=function(){if(!u||!e.body.contains(u)||"loaded"==u.disqusLoaderStatus)return!0;var n,o,i=t.pageYOffset,l=(n=u,o=n.getBoundingClientRect(),{top:o.top+e.body.scrollTop,left:o.left+e.body.scrollLeft}).top;if(l-i>t.innerHeight*r||i-l-u.offsetHeight-t.innerHeight*r>0)return!0;var c,f,p,y=e.getElementById("disqus_thread");y&&y.removeAttribute("id"),u.setAttribute("id","disqus_thread"),u.disqusLoaderStatus="loaded","loaded"==a?DISQUS.reset({reload:!0,config:d}):(t.disqus_config=d,"unloaded"==a&&(a="loading",c=s,f=function(){a="loaded"},(p=e.createElement("script")).src=c,p.async=!0,p.setAttribute("data-timestamp",+new Date),p.addEventListener("load",function(){"function"==typeof f&&f()}),(e.head||e.body).appendChild(p)))};t.addEventListener("scroll",o(i,l)),t.addEventListener("resize",o(i,l)),t.disqusLoader=function(t,n){n=function(t,e){var n,o={};for(n in t)Object.prototype.hasOwnProperty.call(t,n)&&(o[n]=t[n]);for(n in e)Object.prototype.hasOwnProperty.call(e,n)&&(o[n]=e[n]);return o}({laziness:1,throttle:250,scriptUrl:!1,disqusConfig:!1},n),r=n.laziness+1,i=n.throttle,d=n.disqusConfig,s=!1===s?n.scriptUrl:s,(u="string"==typeof t?e.querySelector(t):"number"==typeof t.length?t[0]:t)&&(u.disqusLoaderStatus="unloaded"),l()}}(window,document);

View File

@ -1 +0,0 @@
(function(b,l,e,g,h,f){1!==parseInt(e.msDoNotTrack||b.doNotTrack||e.doNotTrack,10)&&b.addEventListener("load",function(){var r=(new Date).getTime();b.galite=b.galite||{};var m=new XMLHttpRequest,n="https://www.google-analytics.com/collect?cid="+(l.uid=l.uid||Math.random()+"."+Math.random())+"&v=1&tid="+galite.UA+"&dl="+f(h.location.href)+"&ul=en-us&de=UTF-8",a=function(b){var d="",c;for(c in b){if(void 0===b[c])return!1;d+=f(b[c])}return d},p={dt:[h.title],sd:[g.colorDepth,"-bit"],sr:[g.availHeight,"x",g.availWidth],vp:[innerWidth,"x",innerHeight],dr:[h.referrer]},k;for(k in p){var q=k+"="+a(p[k]);q&&(n+="&"+q)}a=function(b,d){var c="",a;for(a in d)c+="&"+a+"="+f(d[a]);return function(){var a=n+c+(galite.anonymizeIp?"&aip=1":"")+"&t="+f(b)+"&z="+(new Date).getTime();if(e.sendBeacon)e.sendBeacon(a);else try{m.open("GET",a,!1),m.send()}catch(t){(new Image).src=a}}};setTimeout(a("pageview",null),100);b.addEventListener("unload",a("timing",{utc:"JS Dependencies",utv:"unload",utt:(new Date).getTime()-r}))})})(window,localStorage,navigator,screen,document,encodeURIComponent)

View File

@ -1,31 +0,0 @@
(() => {
// Theme switch
const body = document.body;
const lamp = document.getElementById("mode");
const toggleTheme = (state) => {
if (state === "dark") {
localStorage.setItem("theme", "light");
body.removeAttribute("data-theme");
} else if (state === "light") {
localStorage.setItem("theme", "dark");
body.setAttribute("data-theme", "dark");
} else {
initTheme(state);
}
};
lamp.addEventListener("click", () =>
toggleTheme(localStorage.getItem("theme"))
);
// Blur the content when the menu is open
const cbox = document.getElementById("menu-trigger");
cbox.addEventListener("change", function () {
const area = document.querySelector(".wrapper");
this.checked
? area.classList.add("blurry")
: area.classList.remove("blurry");
});
})();

View File

@ -1,6 +0,0 @@
/*!
* Simple-Jekyll-Search
* Copyright 2015-2020, Christian Fei
* Licensed under the MIT License.
*/
!function(){"use strict";var i={compile:function(r){return o.template.replace(o.pattern,function(t,e){var n=o.middleware(e,r[e],o.template);return void 0!==n?n:r[e]||t})},setOptions:function(t){o.pattern=t.pattern||o.pattern,o.template=t.template||o.template,"function"==typeof t.middleware&&(o.middleware=t.middleware)}},o={};o.pattern=/\{(.*?)\}/g,o.template="",o.middleware=function(){};var n=function(t,e){var n=e.length,r=t.length;if(n<r)return!1;if(r===n)return t===e;t:for(var i=0,o=0;i<r;i++){for(var u=t.charCodeAt(i);o<n;)if(e.charCodeAt(o++)===u)continue t;return!1}return!0},e=new function(){this.matches=function(t,e){return n(e.toLowerCase(),t.toLowerCase())}};var r=new function(){this.matches=function(e,t){return!!e&&(e=e.trim().toLowerCase(),(t=t.trim().toLowerCase()).split(" ").filter(function(t){return 0<=e.indexOf(t)}).length===t.split(" ").length)}};var u={put:function(t){if(f(t))return p(t);if(function(t){return Boolean(t)&&"[object Array]"===Object.prototype.toString.call(t)}(t))return function(t){var e=[];l();for(var n=0,r=t.length;n<r;n++)f(t[n])&&e.push(p(t[n]));return e}(t);return undefined},clear:l,search:function(t){return t?function(t,e,n,r){for(var i=[],o=0;o<t.length&&i.length<r.limit;o++){var u=function(t,e,n,r){for(var i in t)if(!function(t,e){for(var n=!1,r=0,i=(e=e||[]).length;r<i;r++){var o=e[r];!n&&new RegExp(t).test(o)&&(n=!0)}return n}(t[i],r.exclude)&&n.matches(t[i],e))return t}(t[o],e,n,r);u&&i.push(u)}return i}(s,t,c.searchStrategy,c).sort(c.sort):[]},setOptions:function(t){(c=t||{}).fuzzy=t.fuzzy||!1,c.limit=t.limit||10,c.searchStrategy=t.fuzzy?e:r,c.sort=t.sort||a}};function a(){return 0}var s=[],c={};function l(){return s.length=0,s}function f(t){return Boolean(t)&&"[object Object]"===Object.prototype.toString.call(t)}function p(t){return s.push(t),s}c.fuzzy=!1,c.limit=10,c.searchStrategy=c.fuzzy?e:r,c.sort=a;var d={load:function(t,e){var n=window.XMLHttpRequest?new window.XMLHttpRequest:new ActiveXObject("Microsoft.XMLHTTP");n.open("GET",t,!0),n.onreadystatechange=function(e,n){return function(){if(4===e.readyState&&200===e.status)try{n(null,JSON.parse(e.responseText))}catch(t){n(t,null)}}}(n,e),n.send()}};var h={merge:function(t,e){var n={};for(var r in t)n[r]=t[r],"undefined"!=typeof e[r]&&(n[r]=e[r]);return n},isJSON:function(t){try{return t instanceof Object&&JSON.parse(JSON.stringify(t))?!0:!1}catch(e){return!1}}};var t,m,v,w;function y(t){u.put(t),m.searchInput.addEventListener("input",function(t){-1===[13,16,20,37,38,39,40,91].indexOf(t.which)&&(g(),z(t.target.value))})}function g(){m.resultsContainer.innerHTML=""}function O(t){m.resultsContainer.innerHTML+=t}function z(t){var e;(e=t)&&0<e.length&&(g(),function(t,e){var n=t.length;if(0===n)return O(m.noResultsText);for(var r=0;r<n;r++)t[r].query=e,O(i.compile(t[r]))}(u.search(t),t))}function S(t){throw new Error("SimpleJekyllSearch --- "+t)}t=window,m={searchInput:null,resultsContainer:null,json:[],success:Function.prototype,searchResultTemplate:'<li><a href="{url}" title="{desc}">{title}</a></li>',templateMiddleware:Function.prototype,sortMiddleware:function(){return 0},noResultsText:"No results found",limit:10,fuzzy:!1,exclude:[]},w=function j(t){if(!((e=t)&&"undefined"!=typeof e.required&&e.required instanceof Array))throw new Error("-- OptionsValidator: required options missing");var e;if(!(this instanceof j))return new j(t);var r=t.required;this.getRequiredOptions=function(){return r},this.validate=function(e){var n=[];return r.forEach(function(t){"undefined"==typeof e[t]&&n.push(t)}),n}}({required:v=["searchInput","resultsContainer","json"]}),t.SimpleJekyllSearch=function(t){var n;0<w.validate(t).length&&S("You must specify the following required options: "+v),m=h.merge(m,t),i.setOptions({template:m.searchResultTemplate,middleware:m.templateMiddleware}),u.setOptions({fuzzy:m.fuzzy,limit:m.limit,sort:m.sortMiddleware}),h.isJSON(m.json)?y(m.json):(n=m.json,d.load(n,function(t,e){t&&S("failed to get JSON ("+n+")"),y(e)}));var e={search:z};return"function"==typeof m.success&&m.success.call(e),e}}();

View File

@ -0,0 +1,3 @@
[ZoneTransfer]
ZoneId=3
HostUrl=about:internet

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View File

@ -0,0 +1,4 @@
[ZoneTransfer]
ZoneId=3
ReferrerUrl=https://projects.raspberrypi.org/
HostUrl=https://projects-static.raspberrypi.org/projects/physical-computing/248971027a596f3437da45bafd2bd8a8cc35cb95/en/images/pir_wiring.png

View File

@ -1,14 +0,0 @@
---
layout: none
---
[
{% for post in site.posts %}
{
"title" : "{{ post.title | escape }}",
"tags" : "{{ post.tags | join: ', ' }}",
"url" : "{{ site.baseurl }}{{ post.url }}",
"date" : "{{ post.date }}"
} {% unless forloop.last %},{% endunless %}
{% endfor %}
]

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/assets/favicons/mstile-150x150.png"/>
<TileColor>#2c2c2c</TileColor>
</tile>
</msapplication>
</browserconfig>

55
build.sh Executable file
View File

@ -0,0 +1,55 @@
#! /bin/bash
set -x
rm -rf _site-tmp
mkdir -p _site-tmp
touch _site-tmp/index.md
buildDate="$(date '+%d %b %Y')"
# $1=file $2=add to index
convert_md_to_html() {
ls -la "$1"
fileName=$(basename "${1%.*}")
postDate=$(date '+%d %b %Y' -d "$(stat -c %y "$1" | awk '{print $1}')")
bodyClass="page"
if [ $2 -eq 1 ]; then
bodyClass="post"
echo "- [$(pandoc --template=pandoc-metadata.json "$1" | jq -r .title)](/$fileName.html) _${postDate}_" >> _site-tmp/index.md
fi
pandoc --template=tpl-layout.html --highlight-style=zenburn --metadata buildDate="$buildDate" --metadata postDate="$postDate" --metadata bodyClass="$bodyClass" -f markdown -t html5 "$1" > _site-tmp/"$fileName".html
}
# posts
for post in _posts/*.md; do
convert_md_to_html "$post" 1
done
# drafts
if [ $# -eq 1 ]; then
for post in _drafts/*.md; do
convert_md_to_html "$post" 1
done
fi
# pages
for post in ./*.md; do
convert_md_to_html "$post" 0
done
# index
# is there a better way to invert the file?
echo "$(tac _site-tmp/index.md)" > _site-tmp/index.md
pandoc --template=tpl-layout.html --metadata isIndex=1 --metadata buildDate="$buildDate" --metadata bodyClass="index" -f markdown -t html5 _site-tmp/index.md > _site-tmp/index.html
# static files
cp -r static _site-tmp/static
cp -r assets _site-tmp/assets
cp -r root/* _site-tmp/
# concat css
cat static/reset.css static/styles.css > _site-tmp/static/css.css
rm -rf _site
mv _site-tmp _site

View File

@ -1,12 +0,0 @@
---
title: Dónde encontrarme
permalink: /contact/
layout: page
comments: false
---
## Contacto / Contact
Puedes mandar mail a <correo@sergio.am>, o [@xergio](https://twitter.com/xergio) en twitter.
You can send an email to <correo@sergio.am>, or tweet me at [@xergio](https://twitter.com/xergio).

View File

@ -1,3 +1,4 @@
--- ---
layout: home title: Notas y apuntes para mi yo del futuro.
--- ---
<ul><li><a href="2021-10-02-hi.html">2021-10-02-hi</a></li><li><a href="2022-05-08-drone-gitea-ci-cd.html">2022-05-08-drone-gitea-ci-cd</a></li><li><a href="2022-05-10-openwrt-ipv6-home-lan.html">2022-05-10-openwrt-ipv6-home-lan</a></li><li><a href="2022-05-18-digi-reemplazo-openwrt-ont.html">2022-05-18-digi-reemplazo-openwrt-ont</a></li><li><a href="2022-05-20-openwrt-exclude-clients-dhcp-options.html">2022-05-20-openwrt-exclude-clients-dhcp-options</a></li><li><a href="2022-06-23-backups.html">2022-06-23-backups</a></li><li><a href="2022-06-26-openwrt-ipv6-port-forwarding.html">2022-06-26-openwrt-ipv6-port-forwarding</a></li><li><a href="2022-07-29-lvm.html">2022-07-29-lvm</a></li><li><a href="2022-10-18-docker-alpine-intel-quicksync.html">2022-10-18-docker-alpine-intel-quicksync</a></li><li><a href="2022-10-21-unraid-nas-server-home.html">2022-10-21-unraid-nas-server-home</a></li><li><a href="2022-10-23-windows-wsl-migrate.html">2022-10-23-windows-wsl-migrate</a></li><li><a href="2023-01-23-wireguard-lan-hub.html">2023-01-23-wireguard-lan-hub</a></li><li><a href="2024-03-29-blog-simplification.html">2024-03-29-blog-simplification</a></li></ul>

1
pandoc-metadata.json Normal file
View File

@ -0,0 +1 @@
$meta-json$

12
root/alba.txt Normal file
View File

@ -0,0 +1,12 @@
junio 2023
Intel Core i5-13400 2.5 GHz/4.6 GHz
Gigabyte B760M Gaming X AX DDR4
Kingston FURY Beast DDR4 2666 MHz 8GB CL16
Kioxia Exceria G2 Unidad SSD 1TB NVMe M.2 2280
Mars Gaming MC300 FRGB USB 3.0 Rosa
Tempest PSU Fuente de Alimentación 750W
Tempest Cooler 4Pipes Black RGB
Logitech Brio 300

28
root/andrea.txt Normal file
View File

@ -0,0 +1,28 @@
Medion Akoya E16401 MD62264 Intel Core i5-1135G7/8GB/512GB SSD/16.1"
https://www.pccomponentes.com/medion-akoya-e16401-md62264-intel-core-i5-1135g7-8gb-512gb-ssd-161
Procesador Intel® Core™ i5-1135G7 (4 núcleos, hasta 4.2 GHz con tecnología Intel® Turbo Boost, 8 MB L3 caché)
Memoria RAM 8 GB DDR4
Almacenamiento 512 GB SSD
Unidad óptica No
Display 16.1" (40,7 cm) con tecnología IPS y resolución Full HD (1920 x 1080 píxeles)
Controlador gráfico Intel® Iris Xe Graphics
Conectividad Intel® Wi-Fi AX201 con función Bluetooth 5.1 integrada
Webcam Sí, HD
Micrófono Sí
Audio Sonido de alta resolución Dolby Audio™ con 2 altavoces
Teclado Retroiluminado
Batería 3 celdas, Polímero de litio, 45Wh
Conexiones
1 x USB 3.2 Gen 2 Tipo-C con función DisplayPort
1 x USB 2.0
1 x HDMI
1 x Lector tarjeta Micro SD
1 x Audio Combo
1 x Dc-in
Sistema operativo FreeDOS (SIN SISTEMA OPERATIVO)
Dimensiones (Ancho x Profundidad x Altura) 370 x 23 x 245 mm
Peso 1,95 kg
Color Gris

File diff suppressed because one or more lines are too long

25
root/cande.txt Normal file
View File

@ -0,0 +1,25 @@
https://www.pccomponentes.com/hp-250-g9-intel-core-i5-1235u-16gb-512gbssd-156
HP 250 G9 Intel Core i5-1235U/16GB/512GBSSD/15.6"
Procesador Intel® Core™ i5-1235U (10 núcleos, frecuencia turbo máxima de 4.40GHz, 12 MB Intel® Smart Cache)
Memoria RAM 16GB DDR4-SDRAM 3200MHz (2x8GB)
Almacenamiento 512GB SSD
Unidad óptica No
Display 15.6" (39,6 cm) Full HD (1920 x 1080), Micro-Edge, IPS, Antirreflectante, 250 nits, 45% NTSC
Controlador gráfico Intel Iris Xe Graphics
Conectividad Wi-Fi 6 (802.11ax) Realtek RTL8852BE 2x2 + Bluetooth 5.2
Cámara de portátil HD 720p con micrófonos integrados (2)
Audio Altavoces dobles
Batería 3 celdas, Ion-Litio, 41 Wh
Conexiones
2 x USB 3.2 Gen 1 Tipo A
1 x USB 3.2 Gen 1 Tipo C
1 x Combo de auriculares/micrófono jack 3.5 mm
1 x HDMI 1.4b
1 x Ethernet LAN (RJ-45)
Sistema operativo
Sin Sistema Operativo
Dimensiones (Ancho x Profundidad x Altura) 358 x 242 x 19,9 mm
Peso 1,74 kg
Color Plata

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

12
root/lan.txt Normal file
View File

@ -0,0 +1,12 @@
Digi Fibra Smart PRO 10Gbps (symetric)
Router Zyxel AX7501-B0
Switch Mikrotik CSS610-8G-2S+IN
Transceptor 10Gtek® SFP+ RJ45 10GBASE-T
DAC 10Gtek® SFP+ 10GBASE-CU
Raspberry Pi 3b+ (MagicMirror²)
Raspberry Pi 4b (PyKVM)
PC Ryzen 9 3900X (pc.txt)
Unraid NAS (nas.txt)
Synology DS118 (offsite backup)

26
root/md.txt Normal file
View File

@ -0,0 +1,26 @@
Medion Akoya E15415 Intel Core i5-10210U/8GB/256GB SSD/15.6"
https://www.pccomponentes.com/medion-akoya-e15415-intel-core-i5-10210u-8gb-256gb-ssd-156
Procesador Intel® Core™ i5-10210U
Memoria RAM 8 GB DDR4
Almacenamiento 256 GB SSD
Unidad óptica No
Pantalla 15.6" (39.6 cm) Full HD (1920 x 1080 píxeles) con tecnología IPS
Controlador gráfico Intel UHD
Conectividad Intel® Wi-Fi AC-9462 con función Bluetooth® 5.1 integrada
Webcam Webcam de 1MP y micrófono dual - array integrados
Batería 3 celdas, polímeros de litio
Conexiones
1 x Lector tarjeta SD
1 x USB 3.2 Gen 1 Tipo C soporte salida DisplayPort™ y función de carga
2 x USB 3.2 Gen 1 Tipo A
1 x USB 2.0 Tipo A
1 x HDMI - Out
1 x Audio Combo
1 x DC-in
Sistema operativo SIN SISTEMA OPERATIVO
Dimensiones (Ancho x Profundidad x Altura) 35.9 x 2.26 x 24 cm
Peso 1,78 kg
Color Plata

20
root/nas.txt Normal file
View File

@ -0,0 +1,20 @@
Unraid OS Plus
Fractal Design Node 804
MSI B560M PRO
Intel Core i3-10105
Kingston FURY Beast DDR4 2666MHz 8GB CL16
EVGA 500 GD v2 500W 80+ Gold
NOX HUMMER H-212
PCIe SATA3 x1 6ports
PCIe SATA3 x1 4ports
Toshiba N300 4TB 7200RPM [Parity]
5x 4TB Seagate IronWolf 5900RPM
3x 18TB Toshiba MG09 7200RPM
Crucial MX500 250GB [Cache]
HDMI Dummy Plug
Kingston DataTraveler SE9 32GB
PCIe 10Gtek® X520-DA2
Plex Pass Lifetime

22
root/pc.txt Normal file
View File

@ -0,0 +1,22 @@
MSI MEG X570 ACE
AMD Ryzen 9 3900X [3.8GHz]
Corsair iCUE H150i RGB PRO XT
Corsair Vengeance LPX CMK32GX4M2B3200C16 [3200MHz C16] (4x16GB)
Asus GeForce ROG Strix Gaming GTX 1080Ti [11GB GDDR5]
Corsair 5000D Airflow
Crucial MX500 [250GB]
Samsung 970 EVO Plus [250GB M.2]
Samsung 860 EVO [2TB]
Seagate Barracuda 7200.14 [1TB]
Corsair HX850i
ARCTIC P12 PWM PST [120mm] (x5)
ARCTIC F12 PWM PST [120mm] (x5)
Razer Naga Trinity
Corsair K70 LUX [MX blue]
Sennheiser HD560S
Elgato Wave 3
Acer XV272UP [27" LED IPS Wide QuadHD FreeSync 144Hz] (x2)
APC Back UPS Pro BR900G-GR SAI 900VA
https://es.pcpartpicker.com/user/giomoblin/saved/#view=jpbrvK

1
root/ping.txt Normal file
View File

@ -0,0 +1 @@
pong

BIN
root/sergio.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

4
root/sum.txt Normal file
View File

@ -0,0 +1,4 @@
ASUS STRIX Z270H GAMING
Intel Core i5-7600K
NVIDIA GTX 1080

14
root/tara.txt Normal file
View File

@ -0,0 +1,14 @@
junio 2022
Intel Core i5-11400 2.6 GHz
MSI B560M PRO WIFI
Corsair Vengeance LPX DDR4 3200MHz PC4-25600 8GB CL16
Crucial P2 SSD M.2 2280 250GB PCIe Gen3 x4 NVMe
Tempest Start Torre ATX
Mars Gaming MPII650
EVGA GTX 1060 6GB
Logitech MK295 Combo
Creative Live Cam Sync 1080p V2
Logitech G335

Binary file not shown.

Binary file not shown.

74
static/reset.css Normal file
View File

@ -0,0 +1,74 @@
/* https://piccalil.li/blog/a-more-modern-css-reset/ */
/* Box sizing rules */
*,
*::before,
*::after {
box-sizing: border-box;
}
/* Prevent font size inflation */
html {
-moz-text-size-adjust: none;
-webkit-text-size-adjust: none;
text-size-adjust: none;
}
/* Remove default margin in favour of better control in authored CSS */
body, h1, h2, h3, h4, p,
figure, blockquote, dl, dd {
margin-block-end: 0;
}
/* Remove list styles on ul, ol elements with a list role, which suggests default styling will be removed */
ul[role='list'],
ol[role='list'] {
list-style: none;
}
/* Set core body defaults */
body {
min-height: 100vh;
line-height: 1.5;
}
/* Set shorter line heights on headings and interactive elements */
h1, h2, h3, h4,
button, input, label {
line-height: 1.1;
}
/* Balance text wrapping on headings */
h1, h2,
h3, h4 {
text-wrap: balance;
}
/* A elements that don't have a class get default styles */
a:not([class]) {
text-decoration-skip-ink: auto;
/*color: currentColor;*/
}
/* Make images easier to work with */
img,
picture {
max-width: 100%;
display: block;
}
/* Inherit fonts for inputs and buttons */
input, button,
textarea, select {
font: inherit;
}
/* Make sure textareas without a rows attribute are not tiny */
textarea:not([rows]) {
min-height: 10em;
}
/* Anything that has been anchored to should have extra scroll margin */
:target {
scroll-margin-block: 5ex;
}

BIN
static/sergio.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
static/sergio100w.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

183
static/styles.css Normal file
View File

@ -0,0 +1,183 @@
/* https: //gist.github.com/Albert221/753d7f8955eeb6f5e50486fce048e39f */
@font-face {
font-family: 'JetBrains Mono';
src: url('/static/JetBrainsMono-Regular.woff2') format('woff2'),
url('/static/JetBrainsMono-Regular.woff') format('woff');
font-weight: 400;
font-style: normal;
font-display: swap;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-size: 1.2rem;
font-weight: 500;
line-height: 1.8;
max-width: 900px;
margin: 0 auto;
background-color: #1E1F22; /* Dark background */
color: #CED2D7; /* Main text color */
}
header, footer {
color: #fff;
padding: 10px;
font-size: .9em;
font-weight: bold;
}
header {
/* border-bottom: 3px solid #333; */
border-bottom: 3px solid transparent;
border-image: linear-gradient(0.25turn, #1E1F22, #333, #1E1F22);
border-image-slice: 1;
}
footer {
/* border-top: 3px solid #333; */
border-top: 3px solid transparent;
border-image: linear-gradient(0.25turn, #1E1F22, #333, #1E1F22);
border-image-slice: 1;
text-align: center;
padding-bottom: 20px;
}
nav ul {
list-style-type: none;
text-align: center;
}
nav ul li {
display: inline;
margin-right: 20px;
}
nav ul li a, footer a {
color: #fff;
text-decoration: none;
}
main {
padding: 20px;
}
body.index main {
padding-top: 0;
}
main p {
margin-left: 1rem;
}
main a {
color: #ff5277;
text-decoration: none;
position: relative;
}
/* https: //tobiasahlin.com/blog/css-trick-animating-link-underlines/ */
main a::before {
content: "";
position: absolute;
display: block;
width: 100%;
height: 1px;
bottom: 0;
left: 0;
background-color: #ff5277;
transform: scaleX(0);
transform-origin: top left;
transition: transform 0.2s ease;
}
main a:hover::before {
transform: scaleX(1);
}
main a:visited {
color: #BF6C6C;
}
body.index main ul {
list-style: none;
padding-left: 0;
}
body.index main ul li em {
font-style: normal;
font-size: 0.7rem;
color: #888;
background-color: #2d2d2d;
border-radius: 0.2rem;
padding: 2px 5px;
}
body.index .header {
background: rgb(30, 31, 34);
background: linear-gradient(0.25turn, rgba(30, 31, 34, 1) 0%, rgba(51, 51, 51, 1) 35%, rgba(30, 31, 34, 1) 100%);
text-align: center;
padding: 3rem;
}
body.index .header img {
margin: 0 auto;
border-radius: 100%;
}
a:hover {
color: #ff5277;
}
div.sourceCode, pre {
background-color: #333;
}
pre {
padding: 1rem;
}
pre, code, tt {
font-family: 'JetBrains Mono';
}
p code {
background-color: #2d2d2d;
color: #c4c399;
border-radius: 0.2rem;
padding-left: 5px;
padding-right: 5px;
}
h1, h2, h3, h4, h5, h6 {
background: #ffffff;
background: linear-gradient(to bottom, #ffffff 0%, #00dda6 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 0px 0px 2px rgba(0,0,0,0.2);
}
main div.subtitle {
margin-top: 0px;
color: #555;
font-size: .8em;
}
figure * {
margin: 0 auto;
font-size: 0.9rem;
text-align: center;
}
/* Responsive Styles */
@media only screen and (max-width: 900px) {
header, footer {
width: 100%; /* Full width on mobile */
}
main {
width: 100%; /* Full width on mobile */
max-width: none; /* Remove max-width on mobile */
}
}

Some files were not shown because too many files have changed in this diff Show More