606 - Editando archivos en Docker con Neovim

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

1:25
-3:15

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,

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *