Alfred es un servidor personal en el que tengo los siguientes servicios:
- Torrent box
- NextCloud
Más que un servicio pensado en producción, es un proyecto de aprendizaje y diversión.
Este archivo contiene toda la configuración del servidor y de los servicios expuestos en el mismo. Esta escrito en formato de programación literaria (prosa mas código) y usa las capacidades de orgmode para ejecutar código bash y escribir archivos de configuración de diferente naturaleza.
Este archivo contiene todo lo que se debe hacer para llegar, dese 0, a una configuración estable de Alfred.
La presente configuración se escribe para un Rapsberry Pi 4 B de 8 gigas de RAM usando Raspberry OS de 64 bits. Los detalles técnicos son:
- Raspberry Pi 4 B de 8 gigas de ram
- Tarjeta micro SD SanDisk Extreme de 32 Gb
- Fuente de poder CanaKit USB-C de 5v y 3.5A
- Coraza Canakit con disipadores de calor y enfriamiento activo para Rpi 4 (conectado en pines de 3 voltios para reducir ruido y consumo de energía)
- Disco externo Toshiba Canvio Basics 2TB
Una vez se tenga ensamblado el Raspberry Pi (se recomienda usar enfriamiento activo con este kit), se debe instalar el sistema operativo. Esto se logra usando Raspberry Pi Imager (version 1.5) para instalar Ubuntu Server 20.10 LTS de 64 bits en la tarjeta micro SD.
Dado que no vamos a conectar el RPi a ninguna pantalla ni teclado para hacer la configuración, tenemos que hacerlo todo por SSH. Ubuntu Server tiene habilitado por defecto el servicio de ssh[fn:1] usando el usuario ubuntu:ubuntu
. Para evitar problemas de seguridad, lo primero que nos va a pedir es que cambiemos la contraseña de este usuario. Si conectamos el RPi por puerto ethernet, no hay que hacer nada más por ahora.
Ahora tenemos que cambiar el usuario por defecto (para evitar problemas de seguridad). Esto se hace ejecutando los siguientes comandos en el RPi:
sudo adduser juan
sudo usermod -a -G adm,dialout,cdrom,sudo,audio,video,plugdev,games,users,input,netdev juan
sudo pkill -u ubuntu
sudo deluser ubuntu
Luego de esto, cerramos la sesión de SSH e iniciamos una nuevo con usuario juan
(y nos olvidamos por ahora del usuario ubuntu
) para continuar con la configuración.
Para actualizar el sistema operativo corremos:
sudo apt update
sudo apt full-upgrade
Es recomendable crear una tarea recurrente para mantener actualizado el paquete openssh-server
con parches de seguridad. Eso se hace creando una tarea cron ejecutando:
crontab -e
y agregando la siguiente línea en el archivo mostrado[fn:2]:
# * * * * * command to execute # ┬ ┬ ┬ ┬ ┬ # │ │ │ │ │ # │ │ │ │ │ # │ │ │ │ └───── day of week (0 - 7) (0 to 6 are Sunday to Saturday, or use names; 7 is Sunday, the same as 0) # │ │ │ └────────── month (1 - 12) # │ │ └─────────────── day of month (1 - 31) # │ └──────────────────── hour (0 - 23) # └───────────────────────── min (0 - 59) 0 0 * * * apt install openssh-server
Para evitar ataques de diccionario con usuario pi
(que no se eliminó), es buena idea desactivar el acceso de este usuario por ssh. Se hace agregando DenyUsers pi
al archivo /etc/ssh/sshd_config
y ejecutando:
sudo systemctl restart ssh
Como medida adicional de seguridad, vamos a instalar un firewall ([[https://www.linux.com/learn/introduction-uncomplicated-firewall-ufw][ufw]]
) y fail2ban para que nos automatice algunas tareas de gestión del firewall.
Los siguientes comando hacen esta instalación y configuran el firewall para aceptar conexiones de ssh:
sudo apt install ufw
sudo ufw allow ssh/tcp
sudo ufw enable
sudo ufw limit ssh/tcp
sudo apt install fail2ban
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Ahora tenemos que editar el archivo /etc/fail2ban/jail.local
agregando las siguientes líneas:
[ssh] enabled = true port = ssh filter = sshd logpath = /var/log/auth.log maxretry = 3 bantime = -1
Para determinar la ubicación del disco externo se ejecuta lsblk
. Luego, se ejecutan los siguientes comando para definir punto de montaje del disco y hacer el montaje inicial.
sudo mkdir /media/externo
sudo mount -t ntfs-3g /dev/sda1 /media/externo
Para montar automáticamente, se agrega la siguiente línea a /etc/fstab
(reemplanzando <xxx> con el ID del disco externo que se puede consultar ejecutando blkid /dev/sda1
):
#Disco externo UUID=<xxx> /media/externo ntfs rw,auto,users,exec,nls=utf8,umask=003,gid=46,uid=1000 0 0
Como base para continuar con la configuración del servidor, vamos a instalar las siguientes aplicaciones:
sudo apt install git emacs-nox docker docker.io
De aquí en adelante podemos clonar este repositorio en /home/juan
, abrir README.org en emacs local (a través de conexión ssh) y ejecutar los bloques sh en el servidor con C-c C-c
.
Para usar emacs para la configuración del resto de servidor vamos a crear una configuración mínima que nos facilite la vida. Para desplegarla tenemos que abrir este archivo en emacs y ejecutar M-x org-babel-tagle
(defvar bootstrap-version)
(let ((bootstrap-file
(expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
(bootstrap-version 5))
(unless (file-exists-p bootstrap-file)
(with-current-buffer
(url-retrieve-synchronously
"https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
'silent 'inhibit-cookies)
(goto-char (point-max))
(eval-print-last-sexp)))
(load bootstrap-file nil 'nomessage))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Cargar el org de straight
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(straight-use-package 'use-package)
(setq straight-use-package-by-default t)
(use-package org
:config)
La barra de menú quita espacio y nunca la uso. Además, en terminar no se muestran barras y íconos.
(menu-bar-mode -1)
(tool-bar-mode -1)
Luego de leer https://observer.com/2015/02/meet-the-man-behind-solarized-the-most-important-color-scheme-in-computer-history/, me decidí por Solarized como tema gráfico.
Intenté con solarized-themes, pero usaba fuente no-mono y además los colores en orgmode eran horribles. Uso, entonces, doom-themes.
Para ambientes tty usa misterioso
(if (display-graphic-p)
(use-package doom-themes
:config
;; Global settings (defaults)
(setq doom-themes-enable-bold t ; if nil, bold is universally disabled
doom-themes-enable-italic t) ; if nil, italics is universally disabled
(load-theme 'doom-solarized-dark t))
(load-theme 'misterioso t))
Which-key
es un paquete que muestra los acordes disponibles luego de iniciar un acorde normal o tipo vim. Lo que hace es mostrar un diálogo que muestra las opciones disponibles luego de iniciar un comando. Por ejemplo, si se presiona C-c
, which-key
muestra un diálogo con todos los comandos que se pueden ejecutar luego de C-c
y la descripción de la función a ejecutar. También muestra comando que abren más opciones (e.g. C-c C-x
).
(use-package which-key
:config
(which-key-mode))
(global-visual-line-mode)
(global-auto-revert-mode t)
Por defecto, dired
crea un nuevo buffer cada vez que se da RET
en una carpeta. Si no se quiere abrir tantos buffers, una opción es usar dired-find-alternate-file
(mapeado por defecto a a
) en lugar de de dired-find-file
(mapeado por defecto a RET
). Sin embargo, esta función está deshabilitada porque los usuarios la encontraban confusa. Para habilitarla por defecto y evitar el diálogo de confirmación se debe ejecutar:
(put 'dired-find-alternate-file 'disabled nil)
(use-package all-the-icons-dired
:hook (dired-mode . all-the-icons-dired-mode))
Uso Emacs dashboard para generar la pantalla de inicio en emacs.
(use-package dashboard
:init
(setq dashboard-items '((recents . 5)
(bookmarks . 5)
(projects . 5)
;(registers . 5)
(agenda . 5)))
(setq dashboard-show-shortcuts t)
(setq dashboard-set-heading-icons t)
(setq dashboard-set-file-icons t)
(setq dashboard-set-file-icons t)
(setq dashboard-center-content t)
:config
(add-to-list 'recentf-exclude ; Excluir de recentf archivos que no quiero ver en dasboard
(expand-file-name "~/.emacs.d/excorporate/diary-excorporate-transient"))
(add-to-list 'recentf-exclude ; Excluir de recentf archivos que no quiero ver en dasboard
(expand-file-name "~/.emacs.d/bookmarks"))
(add-to-list 'recentf-exclude ; Excluir de recentf archivos que no quiero ver en dasboard
(expand-file-name "~/.emacs.d/excorporate/diary-excorporate-today"))
(add-to-list 'recentf-exclude ; Excluir de recentf archivos que no quiero ver en dasboard
(expand-file-name "~/.orhc-bibtex-cache"))
(add-to-list 'recentf-exclude ; Excluir de recentf archivos que no quiero ver en dasboard
(expand-file-name "~/.emacs.d/diary"))
(dashboard-setup-startup-hook))
Yasnippet es un paquete que permite la definición de snippets (o plantillas) y su utilización en diferentes modos. Por defecto, no trae plantillas definidas; si se quieren plantillas, se debe agregar el paquete yasnippet-snippets
que trae una colección de plantillas predefinidas para modos populares.
(use-package yasnippet
:defer 1
:diminish yas-minor-mode
:config (yas-global-mode)
(use-package yasnippet-snippets
:after yasnippet
:config (yasnippet-snippets-initialize))
(yas-reload-all))
La definición de plantillas se hace a partir de un archivo de texto plano que se guarda en .emacs.d/snippets/<mode>/
.
(use-package doom-modeline
:init (doom-modeline-mode 1) ; Activar doom-modeline en todos los modos
:config
(setq doom-modeline-modal-icon nil)) ; Usar letras en lugar de íconos para indicar el modo de edición
(use-package rainbow-delimiters
:defer 1
:init
(add-hook 'prog-mode-hook 'rainbow-delimiters-mode))
Helpful es un paquete que mejora los buffers de ayuda estándares de Emacs. Es usado en Doom emacs.
(use-package helpful
:defer 2
:custom
(counsel-describe-function-function #'helpful-callable)
(counsel-describe-variable-function #'helpful-variable))
Luego de leer https://observer.com/2015/02/meet-the-man-behind-solarized-the-most-important-color-scheme-in-computer-history/, me decidí por Solarized como tema gráfico.
Intenté con solarized-themes, pero usaba fuente no-mono y además los colores en orgmode eran horribles.
Instalación y activación:
(if (display-graphic-p)
(use-package doom-themes
:config
;; Global settings (defaults)
(setq doom-themes-enable-bold t ; if nil, bold is universally disabled
doom-themes-enable-italic t) ; if nil, italics is universally disabled
(load-theme 'doom-solarized-dark t))
(load-theme 'misterioso t))
(global-display-line-numbers-mode)
Instalación de íconos para usar en Emacs dashboard y otros paquetes.
(use-package all-the-icons)
Luego de esto hay que correr all-the-icons-install-fonts
desde emacs para instalar las fuent
(use-package magit
:defer 1
:config )
(use-package projectile
:custom
((projectile-enable-caching t) ; Habilitar cache de archivos de proyecto para acelerar
(projectile-globally-ignored-files '("*.org~")) ; extensiones de archivo a ignorar globalmente
(projectile-completion-system 'ivy)) ;Usar ivy como motor de terminación
:config
(projectile-mode)
(setq projectile-enable-caching t)
(use-package counsel-projectile
:config
(counsel-projectile-mode)))
Company se usa para terminación en modos de programación o similares. La terminación se hace a partir de un diálogo flotante en el punto en donde se ingresa un término “terminable”.
(use-package company
:defer 2
:config )
Uso Ivy como framework de completitud. La configuración básica se muestra a continuación:
(use-package ivy
;; Para que no liste a Ivy en la barra de modos activados
:diminish (ivy-mode . "")
:config
;; Lo activa por defecto
(ivy-mode 1))
Activo counsel y enlazo M-x
a counsel-M-x
. Con esto se logra ver los binds actuales de los comandos
(use-package counsel
:config
(global-set-key (kbd "M-x") 'counsel-M-x))
Los siguientes paquetes se usan para agregar íconos a ivy. Por ejemplo, agrega íconos a la selección de buffers.
(use-package all-the-icons-ivy-rich
:init (all-the-icons-ivy-rich-mode 1))
(use-package ivy-rich
:init (ivy-rich-mode 1))
(use-package prescient
:config
(use-package ivy-prescient
:after counsel
:config
(ivy-prescient-mode)
(prescient-persist-mode)))
Es necesario instalar y configurar general
. General
es un paquete que permite la definición de acordes usando teclas lideres (globales y locales) para diferentes modos (e.g. emacs, normal, insert, etc).
(use-package general
:config
;; defniciión de tecla lider global para modo normal.
(general-create-definer leader
;; :prefix leader
:states '(normal insert emacs)
:keymaps 'override
:prefix "SPC"
;; tecla lider desde modos diferente a normal.
:non-normal-prefix "M-SPC")
;; definición de tecla lider local (relativo al major mode) para modo normal.
(general-create-definer local-leader
;; prefix local-leader.
:states '(normal insert emacs)
:prefix "SPC m"
;; tecla lider local desde modos diferentes a normal
:non-normal-prefix "M-SPC m"))
Ahora se carga evil
y se hace la configuración inicial.
(use-package evil
:config
;; Iniciar en modo NORMAL por defecto para todos los modos
(setq evil-default-state 'normal)
;; Agregar modos extraños (que empiezan en E o M) a lista de modos visuales
(add-to-list 'evil-normal-state-modes 'help-mode)
(add-to-list 'evil-normal-state-modes 'help-mode)
;; Arrancar emacs con evil-mode por defecto.
(evil-mode))
Evil-mc es un paquete que permite la creación de multiples cursores cuando se usa evil-mode. Esto se puede usar para hacer ediciones simultáneas en múltiples puntos del mismo buffer.
(use-package evil-mc
:config
(global-evil-mc-mode 1))
(leader
:infix "f"
"" '(:ignore t :which-key "file")
"f" '(counsel-find-file :which-key "find-file")
"F" '(counsel-recentf :which-key "recentf")
"s" '(save-buffer :which-key "save-buffer")
"d" '(j/delete-file-and-buffer :which-key "j/delete-file-and-buffer")
"S" '(write-file :which-key "write-file"))
Algunas opciones de menú de archivo requieren funciones auxiliares:
(defun j/delete-file-and-buffer ()
"Eliminar el archivo actual del disco duro y cierra su buffer"
(interactive)
(let ((filename (buffer-file-name)))
(if filename
(if (y-or-n-p (concat "Do you really want to delete file " filename " ?"))
(progn
(delete-file filename)
(message "Deleted file %s." filename)
(kill-buffer)))
(message "Not a file visiting buffer!"))))
(leader
:infix "w"
"" '(:ignore t :which-key "window")
"d" '(evil-window-delete :which-key "evil-window-delete")
"s" '(evil-window-split :which-key "evil-window-split")
"<" '(evil-window-decrease-width :which-key "reducir ancho")
">" '(evil-window-increase-width :which-key "aumentar ancho")
"j" '(evil-window-down :which-key "evil-window-down")
"q" '(evil-quit-all :which-key "salir de emacs")
"k" '(evil-window-up :which-key "evil-window-up")
"h" '(evil-window-left :which-key "evil-window-left")
"l" '(evil-window-right :which-key "evil-window-right")
"o" '(delete-other-windows :which-key "delete-other-window")
"TAB" '(evil-window-next :which-key "evil-window-next")
"v" '(evil-window-vsplit :which-key "evil-window-vsplit"))
(leader
:infix "b"
"" '(:ignore t :which-key "buffer")
"d" '(kill-this-buffer :which-key "kill-this-buffer")
"k" '(previous-buffer :which-key "previous-buffer")
"-" '(text-scale-adjust :which-key "reducir fuente")
"+" '(text-scale-adjust :which-key "aumentar fuente")
"r" '(revert-buffer :which-key "revert-buffer")
"v" '(visual-line-mode :which-key "visual-line-mode")
"b" '(counsel-switch-buffer :which-key "ivy-switch-buffer")
"l" '(evil-switch-to-windows-last-buffer :which-key "evil-switch-to-windows-last-buffer")
"j" '(next-buffer :which-key "next-buffer"))
(leader
"p" '(:keymap projectile-command-map :which-key "projectile"))
(leader
:infix "h"
"" '(:ignore t :which-key "Help")
"m" '(describe-mode :which-key "describe-mode")
"f" '(counsel-describe-function :which-key "describe-function")
"v" '(counsel-describe-variable :which-key "describe-veariable")
"K" '(describe-key-briefly :which-key "describe-key-briefly")
"w" '(where-is :which-key "where-is")
"F" '(counsel-describe-face :which-key "describe-face")
"c" '(j/abrir-config :which-key "j/abrir-config")
"k" '(helpful-key :which-key "describe-key"))
Para completar algunas de las opciones definidas en el menú de ayuda se necesitan funciones auxiliares:
(defun j/abrir-config ()
"Abre el archivo de configuración"
(interactive)
(find-file "~/.emacs"))
(leader
"SPC" '(evil-normal-state :which-key "evil-normal-state"))
(leader
"y" '(yas-insert-snippet :which-key "insertar plantilla"))
(leader
:infix "e"
"" '(:ignore t :which-key "edición")
"e" '(j/zen-mode :which-key "Zen mode"))
Mapa de movimiento general (cualquier modo)
(general-define-key
:states '(normal)
:infix "g"
"h" '(evil-beginning-of-line :which-key "evil-beginning-of-line")
"J" '(evil-goto-first-line :which-key "evil-goto-first-line")
"K" '(end-of-buffer :which-key "end-of-buffer")
"l" '(evil-end-of-line :which-key "evil-end-of-line"))
(leader
:infix "o"
"" '(:ignore t :which-key "open")
"a" '(org-agenda :which-key "agenda")
"d" '(dired :which-key "dired")
"b" '(eshell :which-key "eshell")
"t" '(org-todo-list :which-key "full TO-DO list"))
(general-define-key
:states '(normal insert emacs)
:keymaps 'dashboard-mode-map
;; Marcas
"r" '(j/dashboard-goto-recent-files :which-key "archivos recientes")
"m" '(j/dashboard-goto-bookmarks :which-key "bookmarks")
"p" '(j/dashboard-goto-projects :which-key "projects")
"a" '(j/dashboard-goto-agenda :which-key "agenda"))
(defun j/dashboard-goto-recent-files ()
"Go to recent files."
(interactive)
(funcall (local-key-binding "r")))
(defun j/dashboard-goto-projects ()
"Go to projects."
(interactive)
(funcall (local-key-binding "p")))
(defun j/dashboard-goto-bookmarks ()
"Go to bookmarks."
(interactive)
(funcall (local-key-binding "m")))
(defun j/dashboard-goto-agenda ()
"Go to agenda."
(interactive)
(funcall (local-key-binding "a")))
(general-define-key
:states '(normal insert emacs)
:keymaps '(helpful-mode-map)
"g" '(helpful-update :which-key "helpful-update")
"RET" '(helpful-visit-reference :which-key "seguir referencia")
"q" '(kill-buffer-and-window :which-key "salir"))
(use-package ibuffer
:config
(add-to-list 'evil-normal-state-modes
'ibuffer-mode))
Orgmode es mi herramienta principal de trabajo. Lo uso para gestión de tareas, conocimiento, construcción de documentos, entre otros.
(setq org-return-follows-link t ; RET se usa para seguir links en orgmode
org-startup-folded t ;Colapsar contenido al abrir un archivo .org
org-startup-align-all-table t ; Empezar con las tablas colapsadas
org-startup-indented t ; Activar org-indent-mode por defecto
org-tags-column 0) ; Quitar espacio entre título y etiquetas
(add-hook 'org-mode-hook ; Desactivar electric-indent-local-mode en orgmode
(lambda () (electric-indent-local-mode -1)))
(setf (alist-get 'file org-link-frame-setup) #'find-file) ; Abrir links en la misma ventana
(setq org-todo-keywords
'((sequence "TODO(t)" "IDEA(i)" "NEXT(n)" "ESPE(e)" "PROY(p)" "|" "DONE(d)")
(sequence "|" "CANC(c)" "FUTU(f)")))
(setq org-todo-keyword-faces
'(("IDEA" . (:foreground "#268bd2" :weight bold))
("PROY" . (:foreground "#d33682" :weight bold))
("NEXT" . (:foreground "#dc322f" :weight bold))
("ESPE" . (:foreground "#b58900" :weight bold))
("DONE" . (:foreground "#859900" :weight bold))
("CANC" . (:foreground "#859900" :weight bold))
("FUTU" . (:foreground "#2aa198" :weight bold))
("TODO" . (:foreground "#6c71c4" :weight bold))))
(setq org-tag-persistent-alist
'(("@Casa" . ?c)
("@Oficina" . ?o)
("@PC" . ?p)
("@Internet" . ?i)
("@Lectura" . ?l)
("@Calle" . ?k)
("@Noche" . ?h)
("@Transmi" . ?t)
("#Docencia" . ?d)
("#Carrera" . ?u)
("#DevP" . ?v)
("#ProyPer" . ?y)
("#Ciclismo" . ?f)
("#IngresoAdicional" . ?s)
("#Puntos" . ?n)
("Urgente" . ?g)
("Corta" . ?r)
("PasarBalon" . ?b)))
Se puede usar company mode para completar rutas al crear links en orgmode. Sin embargo, pasando el argumento universal a org-insert-link
se logra un resultado igual o mejor porque puedo buscar de forma más inteligente.
;(with-eval-after-load org (set-company-backend! 'org-mode 'company-files))
Cada vez que marco como completada (o cancelada) una tarea con repetición, se guarda una línea de cambio de estado en el cuerpo de la tarea. Para que estas líneas de cambio de estado se guarden dentro de un drawer (logrando tareas más limpias), se modifica el valor de la variable org-log-into-drawer
. Esta variable acepta como parámetro el nombre del cajón en donde se quiere guardar las líneas de cambio de estado. También acepta t
, usando LOGBOOK como nombre por defecto del cajón.
(setq org-log-into-drawer "BITÁCORA")
;(setq org-log-into-drawer t)
(setq org-src-tab-acts-natively t)
Orgmode no permite, por defecto, usar letras para los elementos de listas ordenadas. Para activar este compratamiento, hay que asignar la variable org-list-allow-alphabetical
. Como ya se tiene cargado orgmode, hay que ejecutar org-element-update-syntax
para que el cambio surja efecto.
(setq org-list-allow-alphabetical t)
(org-element-update-syntax)
org-table-header-line-mode
fue incluido en org
9.4 y sirve para mostrar, de forma permanente, la primera fila con texto de una tabla a medida que se navega. Es equivalente a congelar la primera fila en editores como LibreOffice calc.
(setq org-table-header-line-p t)
org-superstar
es un paquete que mejora el aspecto visual de archivos org
. En concreto, lo que hace es
(use-package org-superstar
:config
(add-hook 'org-mode-hook
(lambda ()
(org-superstar-mode 1))))
Configuración necesaria para que org-refile tome en cuenta elementos hasta de nivel 4 en el archivo actual.
;; Destinos hasta de nivel 3
(setq org-refile-targets '((org-agenda-files :maxlevel . 3)))
;; Construcción del destino paso a paso
(setq org-refile-use-outline-path 'file)
(setq org-outline-path-complete-in-steps nil)
Para hacer definición y rastreo de hábitos uso un módulo de org-mode
llamado org-habit
. Se usa ol-habit
en lugar de org-habit
porque desde la versión 9.3 de org
se cambió el nombre de los paquetes.
(add-to-list 'org-modules 'ol-habit)
org-edna
ofrece funcionalidades para generar dependencias entre tareas en org
. Lo hace a través de definir disparadores (trigger) y bloqueadores (blocker) en las tareas, y una serie de acciones muy completa. Los triggers se evaluan cada vez que una actividad se marca como completa. Cada trigger debe tener un espacio de aplicación y una acción a ejecutar en cada tarea que esté dentro del espacio de aplicación.
(use-package org-edna
:config
(org-edna-mode))
;; Prioridades de A a D
(setq org-highest-priority ?A)
(setq org-default-priority ?D)
(setq org-lowest-priority ?D)
;; Colores para las prioridades
(setq org-priority-faces '((?A . (:foreground "#dc322f" :weight bold))
(?B . (:foreground "#b58900" :weight bold))
(?C . (:foreground "#2aa198"))
(?D . (:foreground "#859900"))))
(local-leader
:states '(normal insert emacs)
:keymaps 'org-mode-map
"t" '(org-todo :which-key "change TODO state")
"a" '(org-archive-subtree-default :which-key "archieve subtree")
"e" '(org-export-dispatch :which-key "export")
"p" '(org-priority :which-key "set priority")
"w" '(org-copy-special :which-key "org-copy-special")
"D" '(org-insert-drawer :which-key "org-insert-drawer")
"y" '(org-paste-special :which-key "org-paste-special")
"q" '(org-set-tags-command :which-key "set tags")
"r" '(org-refile :which-key "refile")
"o" '(org-set-property :which-key "set property"))
(general-define-key
:states '(normal insert emacs)
:keymaps 'org-mode-map
"TAB" '(org-cycle :which-key "org-cycle")) (general-define-key
:states '(normal)
:keymaps 'org-mode-map
"RET" '(j/dwim-at-point :which-key "org-return"))
Para que org-agenda
arranque en modo visual de evil, tenemos que agregarlo a evil-visual-state-modes
.
(add-to-list 'evil-normal-state-modes 'org-agenda-mode)
También es importante deshabilitar org-super-agenda-map
para evitar bindings por defecto en encabezados de org-super-agenda
. En particular, si no se deshabilita esto, no se pueden usar los bindigs para movimiento de evil en encabezados de org-super-agenda
.
(setq org-super-agenda-header-map (make-sparse-keymap))
(general-define-key
:states '(normal insert emacs)
:keymaps 'org-agenda-mode-map
"i" '(org-agenda-clock-in :which-key "Clock-in")
"o" '(org-agenda-clock-out :which-key "Clock-out")
"g" '(org-agenda-clock-goto :which-key "Go to clocked task")
"c" '(org-agenda-clock-cancel :which-key "Cancel clock")
"e" '(org-agenda-set-effort :which-key "Set effort estimate")
"d" '(org-agenda-entry-text-mode :which-key "Display task text")
"t" '(org-agenda-todo :which-key "change TODO state")
"q" '(org-agenda-quit :which-key "quit")
"RET" '(org-agenda-switch-to :which-key "go to task")
"a" '(org-agenda-archive :which-key "archieve")
"Q" '(org-agenda-set-tags :which-key "set tags")
"r" '(org-agenda-redo :which-key "refresh agenda")
"s" '(org-save-all-org-buffers :which-key "save org buffers")
"I" '(org-clock-in-last :which-key "Clock in last"))
(local-leader
:states '(normal insert emacs)
:keymaps 'org-mode-map
:infix "c"
"" '(:ignore t :which-key "Clock functions")
"i" '(org-clock-in :which-key "Clock-in")
"o" '(org-clock-out :which-key "Clock-out")
"g" '(org-clock-goto :which-key "Go to clocked task")
"c" '(org-clock-cancel :which-key "Cancel clock")
"d" '(org-clock-display :which-key "display clocked time")
"e" '(org-set-effort :which-key "Set effort estimate")
"E" '(org-clock-modify-effort-estimate :which-key "Modify effort")
"I" '(org-clock-in-last :which-key "Clock in last"))
(local-leader
:states '(normal insert emacs)
:keymaps 'org-mode-map
:infix "f"
"" '(:ignore t :which-key "Pie de página")
"f" '(org-footnote-new :which-key "agregar pie de página")
"n" '(org-footnote-normalize :which-key "normalizar pie de página"))
(local-leader
:states '(normal insert emacs)
:keymaps 'org-mode-map
:infix "l"
"" '(:ignore t :which-key "link functions")
"l" '(org-insert-link :which-key "org-insert-link")
"o" '(org-open-at-point :which-key "org-open-at-point"))
(local-leader
:states '(normal insert emacs)
:keymaps 'org-mode-map
:infix "d"
"" '(:ignore t :which-key "date functions")
"d" '(org-deadline :which-key "org-deadline")
"s" '(org-schedule :which-key "org-schedule")
"f" '(j/org-set-futu :which-key "j/org-set-futu")
"t" '(org-time-stamp-inactive :which-key "org-time-stamp-inactive"))
(general-define-key
:states '(normal)
:keymaps 'org-mode-map
"H" '(outline-up-heading :which-key "go to parent heading")
"j" '(evil-next-visual-line :which-key "evil-next-visual-line")
"k" '(evil-previous-visual-line :which-key "evil-previous-visual-line")
"J" '(evil-next-line :which-key "evil-next-line")
"K" '(evil-previous-line :which-key "evil-previous-line"))
(local-leader
:states '(normal insert emacs)
:keymaps 'org-mode-map
:infix "b"
"" '(:ignore t :which-key "date functions")
"c" '(org-table-convert :which-key "org-table-convert")
"TAB" '(org-table-shrink :which-key "org-table-shrink")
"d" '(org-edit-special :which-key "org-edit-special"))
;; (local-leader
;; :states '(normal insert emacs)
;; :keymaps 'table-cell-map
;; :infix "b"
;; "" '(:ignore t :which-key "table functions")
;; "p" '(table-span-cell :which-key "table-span-cell")
;; "f" '(table-justify :which-key "table-justify")
;; "j" '(table-heighten-cell :which-key "table-heighten-cell")
;; "k" '(table-shorten-cell :which-key "table-shoren-cell")
;; "s" '(table-split-cell-horizontally :which-key "table-split-cell-horizontally")
;; "v" '(table-split-cell-vertically :which-key "table-split-cell-vertically")
;; "i" '(table-insert-row-column :which-key "table-insert-row-column")
;; "<" '(table-narrow-cell :which-key "table-narrow-cell")
;; ">" '(table-widen-cell :which-key "table-widen-cell"))
La siguiente función dwim (do what I mean) se toma del código de Doom. Se le modifica el nombre para conservar convención de nombramiento de funciones propias.
(defun j/dwim-at-point (&optional arg)
"Do-what-I-mean at point.
If on a:
- checkbox list item or todo heading: toggle it.
- clock: update its time.
- headline: cycle ARCHIVE subtrees, toggle latex fragments and inline images in
subtree; update statistics cookies/checkboxes and ToCs.
- footnote reference: jump to the footnote's definition
- footnote definition: jump to the first reference of this footnote
- table-row or a TBLFM: recalculate the table's formulas
- table-cell: clear it and go into insert mode. If this is a formula cell,
recaluclate it instead.
- babel-call: execute the source block
- statistics-cookie: update it.
- latex fragment: toggle it.
- link: follow it
- otherwise, refresh all inline images in current tree."
(interactive "P")
(let* ((context (org-element-context))
(type (org-element-type context)))
;; skip over unimportant contexts
(while (and context (memq type '(verbatim code bold italic underline strike-through subscript superscript)))
(setq context (org-element-property :parent context)
type (org-element-type context)))
(pcase type
(`headline
(cond ((memq (bound-and-true-p org-goto-map)
(current-active-maps))
(org-goto-ret))
((and (fboundp 'toc-org-insert-toc)
(member "TOC" (org-get-tags)))
(toc-org-insert-toc)
(message "Updating table of contents"))
((string= "ARCHIVE" (car-safe (org-get-tags)))
(org-force-cycle-archived))
((or (org-element-property :todo-type context)
(org-element-property :scheduled context))
(org-todo
(if (eq (org-element-property :todo-type context) 'done)
(or (car (+org-get-todo-keywords-for (org-element-property :todo-keyword context)))
'todo)
'done))))
;; Update any metadata or inline previews in this subtree
(org-update-checkbox-count)
(org-update-parent-todo-statistics)
(when (and (fboundp 'toc-org-insert-toc)
(member "TOC" (org-get-tags)))
(toc-org-insert-toc)
(message "Updating table of contents"))
(let* ((beg (if (org-before-first-heading-p)
(line-beginning-position)
(save-excursion (org-back-to-heading) (point))))
(end (if (org-before-first-heading-p)
(line-end-position)
(save-excursion (org-end-of-subtree) (point))))
(overlays (ignore-errors (overlays-in beg end)))
(latex-overlays
(cl-find-if (lambda (o) (eq (overlay-get o 'org-overlay-type) 'org-latex-overlay))
overlays))
(image-overlays
(cl-find-if (lambda (o) (overlay-get o 'org-image-overlay))
overlays)))
(+org--toggle-inline-images-in-subtree beg end)
(if (or image-overlays latex-overlays)
(org-clear-latex-preview beg end)
(org--latex-preview-region beg end))))
(`clock (org-clock-update-time-maybe))
(`footnote-reference
(org-footnote-goto-definition (org-element-property :label context)))
(`footnote-definition
(org-footnote-goto-previous-reference (org-element-property :label context)))
((or `planning `timestamp)
(org-follow-timestamp-link))
((or `table `table-row)
(if (org-at-TBLFM-p)
(org-table-calc-current-TBLFM)
(ignore-errors
(save-excursion
(goto-char (org-element-property :contents-begin context))
(org-call-with-arg 'org-table-recalculate (or arg t))))))
(`table-cell
(org-table-blank-field)
(org-table-recalculate arg)
(when (and (string-empty-p (string-trim (org-table-get-field)))
(bound-and-true-p evil-local-mode))
(evil-change-state 'insert)))
(`babel-call
(org-babel-lob-execute-maybe))
(`statistics-cookie
(save-excursion (org-update-statistics-cookies arg)))
((or `src-block `inline-src-block)
(org-babel-execute-src-block arg))
((or `latex-fragment `latex-environment)
(org-latex-preview arg))
(`link
(let* ((lineage (org-element-lineage context '(link) t))
(path (org-element-property :path lineage)))
(if (or (equal (org-element-property :type lineage) "img")
(and path (image-type-from-file-name path)))
(+org--toggle-inline-images-in-subtree
(org-element-property :begin lineage)
(org-element-property :end lineage))
(org-open-at-point arg))))
((guard (org-element-property :checkbox (org-element-lineage context '(item) t)))
(let ((match (and (org-at-item-checkbox-p) (match-string 1))))
(org-toggle-checkbox (if (equal match "[ ]") '(16)))))
(_
(if (or (org-in-regexp org-ts-regexp-both nil t)
(org-in-regexp org-tsr-regexp-both nil t)
(org-in-regexp org-link-any-re nil t))
(call-interactively #'org-open-at-point)
(+org--toggle-inline-images-in-subtree
(org-element-property :begin context)
(org-element-property :end context)))))))
(defun +org--toggle-inline-images-in-subtree (&optional beg end refresh)
"Refresh inline image previews in the current heading/tree."
(let ((beg (or beg
(if (org-before-first-heading-p)
(line-beginning-position)
(save-excursion (org-back-to-heading) (point)))))
(end (or end
(if (org-before-first-heading-p)
(line-end-position)
(save-excursion (org-end-of-subtree) (point)))))
(overlays (cl-remove-if-not (lambda (ov) (overlay-get ov 'org-image-overlay))
(ignore-errors (overlays-in beg end)))))
(dolist (ov overlays nil)
(delete-overlay ov)
(setq org-inline-image-overlays (delete ov org-inline-image-overlays)))
(when (or refresh (not overlays))
(org-display-inline-images t t beg end)
t)))
(defun +org-get-todo-keywords-for (&optional keyword)
"Returns the list of todo keywords that KEYWORD belongs to."
(when keyword
(cl-loop for (type . keyword-spec)
in (cl-remove-if-not #'listp org-todo-keywords)
for keywords =
(mapcar (lambda (x) (if (string-match "^\\([^(]+\\)(" x)
(match-string 1 x)
x))
keyword-spec)
if (eq type 'sequence)
if (member keyword keywords)
return keywords)))
[fn:2] Las líneas de comentario se agregan para tener una explicación de la tarea cron y cuándo se va a ejecutar.
[fn:1] Se recomienda usar la aplicación movil Fing para encontrar el ip asignado al RPi en la red local.