606 - Editando archivos en Docker con Neovim
Editar archivos en #docker con #neovim y como instalar #aplicaciones en decenas o cientos de servidores y #vps sin mover un dedo y de forma eficiente
Como te comenté en el anterior episodio, he estado actualizando determinadas páginas web y sobre todo la última me ha llevado bastante trabajo. Y me ha llevado bastante trabajo porque era una versión donde algunos plugins de la misma ya estaban descatalogados, con lo cual ha sido necesario adaptarlo a la nueva versión de PHP. Dado que la página web estaba desplegada mediante contenedores, tenía que acceder a los mismos y modificar los archivos. En este episodio te cuento como lo hacía y como lo hago. Y que solución he adoptado para distribuir software entre los distintos VPS que estoy utilizando. Así vamos directos a editar archivos en Docker con Neovim.
Editando archivos en Docker con Neovim
Aviso para críticos
Tengo muy claro que no debería hacer lo que he estado haciendo, modificar archivos directamente en producción. Creas o no lo creas, esto es algo que me molesta sobremanera, y es que algo que solucionas directamente en producción, puede que luego no se traslade a los repositorios, y tarde o temprano vuelva a ocurrir, y tengas que volver a investigar que es lo que está pasando y como solucionarlo.
Sin embargo, y que no sirva de excusa, lo cierto es que dado que la página no estaba funcionando, decidí hacerlo a lo loco y resolver el problema de forma fulminante, y esto me llevó a tener que instalar vim y neovim dentro del contenedor o contenedores en repetidas ocasiones, y arriesgarme a los problemas que tanto me molesta.
Opciones para editar un archivo en un contenedor
Lo cierto cierto, y no nos equivoquemos, es que ese archivo no está en un contenedor, la realidad, es que está dentro de tus sistema de archivos, con lo que en cualquier momento podrías ir al directorio en el que se encuentran y olvidarte del asunto. O también fácilmente, podrías, montarlos como bind en lugar de como volúmenes y sencillo. Pero, hace algún tiempo decidí que me decantaba por los volúmenes por cuestiones prácticas de permisos.
Así, si tienes un archivo en un contenedor, puedes editarlo de varias formas. Por ejemplo,
- Puedes copiar el archivo a tu sistema de archivos, editarlo y devolverlo nuevamente al contenedor.
- Puedes instalar vim o neovim en el contenedor y editarlo. Esto tiene el inconveniente, y es lo que me estaba sucediendo, que cada vez que tienes que editar un archivo, tienes que reinstalar neovim si has reiniciado el contenedor. Este caso me molesta muchísimo.
- Puedes crear una imagen con Vim o Neovim y levantar un contenedor que comparta el volumen. Esta es una de las opciones que he hecho en alguna que otra ocasión, pero lo cierto es que tiene el problema de los complementos de Neovim, y de Neovim en si, es decir, se actualizan tan a menudo, que es imposible tener una imagen lista con lo último.
Un complemento
Como de costumbre, la solución era mucho mas sencilla, simplemente se trataba de instalar un complemento para Neovim. Este complemento me permite dado un volumen y una ruta, editar el archivo con Neovim instalado directamente en el Host, con lo que me puedo olvidar por completo, de cualquier solución anterior. Simplemente tengo que utilizar,
:DockerEdit volumen:/ruta/del/archivo
No creas que no tenía mis dudas, en el sentido de los permisos, pero lo cierto es que no me tengo que preocupar nada mas que de acertar con la ruta, y con esto lo tengo resuelto. Ha sido todo un acierto y una comodidad.
Instalación en servidores
Algo que me gustaba de la opción de tener Neovim en una imagen Docker con sus complementos, es que no tengo nada mas que desplegarlo en el VPS en el que quiero trabajar y listo. Y es que, mientras tienes un VPS, la cuestión es bastante sencilla. Pero, que pasa cuando tienes 5 o 6 VPS, ¿como instalar la misma versión de Neovim?¿Como tenerlo todo actualizado y funcionado?¿Como hacerlo de forma sencilla y rápida?
La solución es bien sencilla y se llama Ansible. Aunque, tampoco es necesario que ni siquieras utilices Ansible, puedes hacerlo incluso directamente con un script en Bash. Para ello tienes que utilizar Semaphore.
Semaphore
Sobre esta herramienta ya te hablé en el episodio 489 del podcast que titulé Semaphore, ansible y hardening, y due después de un a un taller de hardening en Linux e introducción al hardening con Ansible en Linux Center de la mano de Slimbook e impartido por Jesús Amorós.
Si no lo conoces, es una interfaz web desde la que puedes lanzar playbooks de Ansible sobre servidores. Pero que no te asuste esto, porque realmente, puedes lanzar sencillos scripts en Bash, si no dominas el mundo de Ansible. Lo potente del caso, es que te permite ejecutar un mismo script en decenas de máquinas de forma simultánea. Por ejemplo, si tienes varias Raspberry, las podrías actualizar de un solo golpe, ejecutando Semaphore.
Para mi Semaphore es una herramienta similar a Rundeck, muy sencilla, y con prestaciones reducidas. Quiero decir, que no puedes hacer todo lo que se puede hacer con Rundeck, pero en general, para la mayoría de lo que hago yo, es mucho mas que suficiente.
¿Como trabajo?
Básicamente para trabajar con Semaphore, lo que creo es un inventario de las máquinas sobre las que quiero actuar. Esto es realmente sencillo, y lo puedes gestionar directamente, desde la propia página web de Semaphore. En mi caso, yo normalmente tengo varios inventarios. Por ejemplo, un inventarios de VPS, otro inventario de Raspberry, un inventario de equipos, etc. Mas o menos se trata de equipos de usos parecidos.
Además de los distintos inventarios tengo un repositorio de Git donde tengo playbooks de ansible. Estos playbooks de Ansible me permiten hacer las operaciones mas variopintas. Desde por ejemplo, actualizar un VPS, hasta aplicar determinadas reglas de hardening, o como este caso, instalar un determinado software con su configuración.
Vuelvo a insistir en que realmente tampoco necesitas conocer Ansible, que puedes hacerlo perfectamente utilizando scripts en Bash. De echo, por pereza, en mi caso, la instalación y configuración de Neovim, la hago de esta forma aunque utilice un playbook, fíjate,
---
- hosts: all
gather_facts: true
become: true
tasks:
- name: Install last version of Neovim in AppImage form
shell: |
mkdir -p /home/lorenzo/.local/bin
cd /home/lorenzo/.local/bin
rm -rf nvim.appimage nvim
curl -LO https://github.com/neovim/neovim/releases/latest/download/nvim.appimage
chmod u+x nvim.appimage
chown lorenzo:lorenzo nvim.appimage
ln -s nvim.appimage nvim
chown lorenzo:lorenzo nvim
rm -rf /home/lorenzo/.config/nvim
mkdir -p /home/lorenzo/.config/nvim
cd /home/lorenzo/.config/nvim
cat <<EOF > init.lua
require("settings")
require("lazy-config")
EOF
mkdir lua
cd lua
cat <<EOF > lazy-config.lua
-----------------------------------------------------------
-- Plugin manager configuration file
-----------------------------------------------------------
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
vim.fn.system({
"git",
"clone",
"--filter=blob:none",
"https://github.com/folke/lazy.nvim.git",
"--branch=stable", -- latest stable release
lazypath,
})
end
vim.opt.rtp:prepend(lazypath)
require("lazy").setup("plugins")
EOF
cat <<EOF > settings.lua
-----------------------------------------------------------
-- Neovim settings
-----------------------------------------------------------
-----------------------------------------------------------
-- Neovim API aliases
-----------------------------------------------------------
local cmd = vim.cmd -- execute Vim commands
local exec = vim.api.nvim_exec -- execute Vimscript
local fn = vim.fn -- call Vim functions
local g = vim.g -- global variables
local opt = vim.opt -- global/buffer/windows-scoped options
local api = vim.api -- call Vim api
local ag = vim.api.nvim_create_augroup -- create autogroup
local au = vim.api.nvim_create_autocmd -- create autocomand
-----------------------------------------------------------
-- General
-----------------------------------------------------------
g.mapleader = ';' -- change leader to a comma
opt.mouse = 'a' -- enable mouse support
opt.clipboard = 'unnamedplus' -- copy/paste to system clipboard
opt.swapfile = false -- don't use swapfile
g.himalaya_mailbox_picker = 'native'
-- Configuracion para Typst
g.typst_conceal = 1
g.typst_pdf_viewer = "zathura"
g.typst_embedded_languages = { "typescript" }
-----------------------------------------------------------
-- Neovim UI
-----------------------------------------------------------
opt.number = true -- show line number
opt.relativenumber = true -- show line number
opt.showmatch = true -- highlight matching parenthesis
opt.foldmethod = 'expr' -- enable folding (default 'foldmarker')
--opt.colorcolumn = '80' -- line lenght marker at 80 columns
opt.splitright = true -- vertical split to the right
opt.splitbelow = true -- orizontal split to the bottom
opt.ignorecase = true -- ignore case letters when search
opt.smartcase = true -- ignore lowercase for the whole pattern
opt.linebreak = true -- wrap on word boundary
opt.foldlevel = 99 -- should open all folds
opt.conceallevel = 0
opt.termguicolors = true
opt.guifont = "JetBrainsMono Nerd Font"
vim.o.cursorline = true
vim.o.number = true
vim.o.termguicolors = true
-----------------------------------------------------------
-- Folding
-----------------------------------------------------------
opt.foldmethod = 'expr'
opt.foldexpr = 'nvim_treesitter#foldexpr()'
opt.list = true
opt.listchars = 'tab:▸ ,space:·,nbsp:␣,trail:•,precedes:«,extends:»'
-----------------------------------------------------------
-- Memory, CPU
-----------------------------------------------------------
opt.hidden = true -- enable background buffers
opt.history = 100 -- remember n lines in history
-- opt.lazyredraw = true -- faster scrolling
opt.synmaxcol = 1000 -- max column for syntax highlight
-----------------------------------------------------------
-- Tabs, indent
-----------------------------------------------------------
opt.expandtab = true -- use spaces instead of tabs
opt.shiftwidth = 4 -- shift 4 spaces when tab
opt.tabstop = 4 -- 1 tab == 4 spaces
opt.smartindent = true -- autoindent new lines
-- don't auto commenting new lines
cmd [[au BufEnter * set fo-=c fo-=r fo-=o]]
-- remove line lenght marker for selected filetypes
cmd [[autocmd FileType text,markdown,xml,html,xhtml,javascript setlocal cc=0]]
-- IndentLine
--g.indentLine_setColors = 0 -- set indentLine color
g.indentLine_char = '|' -- set indentLine character
au(
"BufEnter",
{
pattern = "markdown",
callback = function()
vim.g.indentLine_enabled = 0
end
}
)
-----------------------------------------------------------
-- Highlight
-----------------------------------------------------------
-- highlight yanked text
au(
"TextYankPost",
{
pattern = '*',
callback = function()
vim.highlight.on_yank { higroup = 'IncSearch', timeout = 700 }
end,
group = ag('yank_highlight', {}),
}
)
-----------------------------------------------------------
-- Spell
-----------------------------------------------------------
-- enable spanish spell on markdown only
local markdown_spell = ag("markdownSpell", {})
au(
"FileType",
{
pattern = "markdown",
callback = function()
vim.opt.spelllang = "es"
vim.opt.spell = true
end,
group = markdown_spell
}
)
au(
{ "BufRead", "BufNewFile" },
{
pattern = "*.md",
callback = function()
vim.opt.spelllang = "es"
vim.opt.spell = true
end,
group = markdown_spell
}
)
-----------------------------------------------------------
-- Keybinding
-----------------------------------------------------------
vim.keymap.set({"n", "i"}, "<C-n>", "<cmd>Neotree toggle<cr>")
EOF
mkdir plugins
cd plugins
cat <<EOF > ayu.lua
return {
"Shatur/neovim-ayu",
lazy = false,
config = function()
local opt = vim.opt
opt.termguicolors = true -- enable 24-bit RGB colors
require('ayu').setup({
mirage = false,
overrides = {
tkLink = {fg = "#39BAE6"},
tkBrackets = {fg = "#FF8F40"},
tkHighLight = {fg = "#ABB0B6"},
tkTagSep = {fg = "#399E66"},
tkTag = {fg = "#F27083"},
Visual = {fg = "#000000", bg = "#FFC1D5"},
},
})
require('ayu').colorscheme()
end
}
EOF
cat <<EOF > docker.lua
return {
"kit494way/docker.vim"
}
EOF
cat <<EOF > neotree.lua
return {
"nvim-neo-tree/neo-tree.nvim",
branch = "v3.x",
lazy = false,
dependencies = {
"nvim-lua/plenary.nvim",
"kyazdani42/nvim-web-devicons",
"MunifTanjim/nui.nvim",
"3rd/image.nvim",
},
}
EOF
chown -R lorenzo:lorenzo /home/lorenzo/.config/nvim
Es toda un trampa, si te das cuenta, simplemente he envuelto mi script de instalción y configuración de Neovim en un playbook, para no calentarme nada la cabeza.
Instalando Neovim
Inicialmente comencé a instalar Neovim desde repositorio, el problema, es que en los repositorios oficiales de Ubuntu está la versión 0.7
o similar, y muchos de los plugins no funcionan con esa versión. Y además tiene otro problema, no quiero instalar mucho software, ni menos repositorios extra, porque se trata de servidores que estoy utilizando en producción para alojar una o varias páginas web. Imagina, que alguno de estos repositorio extra, añade alguna incompatibilidad. Ya la tenemos liada.
Por esta razón decidí tirar directamente de AppImage que para esto funciona a las mil maravillas y no tengo que preocuparme de absolutamente nada mas. Los complementos los instalo directamente en la máquina, utilizando el gestor de complementos Lazy
y listo.
Más información,