LINUX.ORG.RU

Статьи

Активные теги:

 , ,

Настройка Neovim/Nvim

Статьи — Разработка

Введение

Neovim или просто Nvim — это современный редактор, возникший как замена Vim.

Vim — это текстовый редактор для терминала, уникальный прежде всего наличием различных режимов работы (нормальный, редактирования, выделения, замены, командный), которые позволяют выполнять абсолютно все действия с клавиатуры без использования мыши, что заметно увеличивает скорость работы с текстом. Так же он является программируемым, что, наверное, и является его самым большим плюсом.

Да, вы можете взять VS Code, поставить кучу плагинов чтобы получить необходимый функционал, но сам редактор, плагины настраиваются через JSON, что не позволяет добавить какую-нибудь пользовательскую функцию для сортировки файлов в проводнике или навесить сочетание на вызов функции… Там такое можно сделать через написание расширений, что намного сложнее и трудозатратнее чем в том же виме + в последнем меньше ограничений со стороны APIVS Code нельзя радикально переделать интерфейс, заменить проводник на другой и т.п.).

Vim возник более 30 с лишним лет назад. Поэтому, в нём нет многих привычных вещей, например, нативной поддержки LSP, которые просто не существовали в момент его создания. Вместо этого vim имеет встроенную поддержку утилиты ctags, которая была чем-то прорывным в 1991-м, но сейчас ею почти никто не пользуется. Vim нельзя назвать мёртвым: он активно развивается, обрастая функционалом. Но то, что в нем для настраивания используется свой собственный язык Vimscript, тормозит его внедрение.

LSP — это протокол, используемый текстовыми редакторами для автодополнения, получения справки и т. д., ставший стандартом в отрасли. Он был придуман Microsoft и так понравился всем, что сейчас почти нет современных редакторов, которые бы его не поддерживали. LSP представляет собой сетевой клиент-серверный протокол, работающий через JSON-RPC. Это значит, что данные могут запрашиваться и получаться по сети (в основном по локальной, но никто не запрещает через Интернет). Нет принципиальной невозможности реализовать клиент на Vimscript, но скорость работы последнего будет оставлять желать лучшего, так как Vimscript не является сетевым языком как и языком общего назначения, поэтому в нём нет поддержки тредов (он однопоточный) и асинхронности, которые бы ускоряли автодополнение. Не говоря уже о том, что Vimscript просто тормоз.

Замена Vimscript на Lua в Neovim решала эти проблемы. Lua является стандартным встраиваемым языком для скриптинга в играх или для настроек в программах. Сам язык напоминает что-то среднее между JS без классов и чем-то похожим на Pascal наличием оператора end. Lua не просто быстрый, он очень быстрый, так как поддерживает JIT-компиляцию.

Nvim имеет обратную совместимость с Vim, он поддерживает как задание настроек через vim-файлы, так и почти все его настройки за небольшим исключением. Это означает, что большинство плагинов для Vim будут работать в Nvim.

Установка Neovim в моём дистрибутиве выглядит так:

sudo pacman -S nvim

Во всех популярных дистрибутивах Nvim можно поставить стандартным пакетным менеджером.

Настройки

Главный файл настроек в Nvim располагается в ~/.config/nvim. Он должен называться init.vim или init.lua.

Но для задания конфигов мы будем использовать именно Lua, так как последний поддерживает полноценные модули.

У нас будет такая структура:

~/.config/nvim
├── init.lua
└── lua
    ├── configs
    │   ├── autocmds.lua
    │   ├── keymaps.lua
    │   ├── options.lua
    │   └── plugins.lua
    └── utils.lua

В директории с конфигом находится директория lua. Такое название является обязательным. Файлы в ней можно подключать через require. В lua для подключаемых конфигов я создал configs.

В качестве пакетного менеджера в Nvim часто используют старый, добрый vim-plug, так как он позволяет использовать старый vimrc вместо написания нового конфига. Для наших же целей он не подходит. Мы будем использовать lazy.nvim, который на данный момент является лучшим пакетным менеджером.


lazy.nvim по умолчанию грузит модули асинхронно. Вот небольшая справка по его использованию:

-- Основной вызов Lazy.nvim
require("lazy").setup(opts)

-- Где opts — это таблица (массив) с описаниями плагинов.
-- Каждый элемент таблицы описывает один плагин.

{
  -- Указывается репозиторий плагина на GitHub
  "github_user/repo",

  -- Необязательное поле: зависимости (будут установлены автоматически)
  dependencies = {
    "github_user/dependency1",
    "github_user/dependency2",
    -- и т.д.
  },

  -- Настройка плагина: вызывается при его загрузке
  -- Можно использовать для ручной инициализации, настройки событий и т.д.
  config = function()
    local plugin = require("plugin_name")
    plugin.setup({
      option1 = true,
      option2 = "value",
    })
  end,

  -- Альтернатива config: если плагин поддерживает setup(opts),
  -- можно указать опции напрямую, и lazy.nvim сам вызовет setup(opts)
  opts = {
    option1 = true,
    option2 = "value",
  },

  -- Если устраивают настройки по умолчанию
  -- (и setup вызывается без параметров), можно передать пустую таблицу
  opts = {},

  -- Или можно просто указать config = true, если плагин сам настроится при require
  config = true,

  -- Указывает, загружать ли плагин по требованию (true по умолчанию).
  -- Устанавливаем lazy = false, если плагин должен быть загружен сразу (например, тема или ключи)
  lazy = false,

  -- Определение горячих клавиш
  keys = {
    { "<leader>ff", "<cmd>Telescope find_files<cr>", desc = "Поиск файлов" },
    { "<C-s>", ":w<CR>", desc = "Сохранить файл", mode = "n" },
    -- mode по умолчанию = "n", можно задать "i", "v", "n", "t" и т.п.
  },

  -- Можно указать команду или событие, при котором плагин будет загружен:
  cmd = { "SomeCommand" },
  event = { "BufReadPost", "BufNewFile" },
}

-- Альтернатива: можно передать путь к модулю, экспортирующему список плагинов
require("lazy").setup("path.to.plugins")

-- Где path/to/plugins.lua может выглядеть как:
return {
  {
    "github_user/foo",
    opts = {},
  },
  {
    "github_user/bar",
    config = function()
      require("bar").setup()
    end,
  },
}

-- Или если в каталоге `lua/path/to/plugins/` несколько файлов:
-- каждый файл экспортирует таблицу(ы) с описанием плагинов:
-- plugins/foo.lua:
return {
  "github_user/foo",
  opts = {},
}

-- plugins/bar.lua:
return {
  "github_user/bar",
  config = true,
}

-- Ошибка: модуль не найден
local foo = require("foo")
require("lazy").setup({ ... })

-- Так же приведет к ошибке, так как модуль ленивый
require("lazy").setup({
  {
    "github_user/foo",
    lazy = true, -- это значение по умолчанию
  },
})

local foo = require("foo")

-- Таким образом, самый надежный вариант
{
  "github_user/foo",
  dependencies = { "github_user/bar" },  -- зависимости тоже гарантированно доступны для загрузки
  config = function()
    local foo = require("foo")
    foo.setup {}
    local bar = require("bar")
    bar.setup()
  end,
}

В init.lua у нас содержится код для автоматической установки lazy.nvim, а также подключение различных модулей с настройками:

-- disable netrw at the very start of your init.lua
vim.g.loaded_netrw = 1
vim.g.loaded_netrwPlugin = 1

-- Установка lazy.nvim
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",
    lazypath
  })
end
vim.opt.rtp:prepend(lazypath)

-- Клавиша leader должна быть задана до загрузки плагинов
vim.g.mapleader = ' '

-- Настройка плагинов
require("lazy").setup("configs.plugins")

-- Настройки задаем после загрузки плагинов
require("configs.options")
require("configs.keymaps")
require("configs.autocmds")

options.lua — это общие настройки, которые я позаимствовал из своего старого конфига Vim за небольшими отличиями. Например, в Nvim set hidden — это поведение по умолчанию, а paste вообще выбросили.

lua/configs/options.lua

local config_dir = vim.fn.stdpath('config')
local opt = vim.opt

-- Настройки интерфейса
opt.termguicolors = true
opt.lazyredraw = true
opt.background = "dark"
opt.guifont = "JetBrainsMono Nerd Font:h12"
opt.guicursor = "n-v-c:block,i-ci-ve:ver25,r-cr:hor20,o:hor20"

-- Нумерация строк
opt.number = true
--opt.relativenumber = true

-- Подсветка текущей строки
opt.cursorline = true

-- Прокрутка
opt.scrolloff = 5     -- по вертикали
opt.sidescrolloff = 5 -- по горизонтали

-- Отображение различных элементов
opt.signcolumn = "yes"
opt.showcmd = false
opt.laststatus = 3
opt.showmode = false -- не показывать режим (--INSERT и тп) в самом низу

-- Перенос строк
opt.wrap = true
opt.linebreak = true

-- Отображение различных символов
opt.list = true
opt.listchars = {
  tab = '→ ',
  trail = '·',
  nbsp = '␣',
  extends = '❯',
  precedes = '❮'
}
opt.fillchars = { eob = " " } -- вместо ~ отображаем просто пустые строки

-- Отступы и табуляция
opt.expandtab = true -- заменять символы табуляции на пробелы при отрисовке
opt.tabstop = 4      -- на сколько пробелов заменяется символ табуляции при отображении
opt.shiftwidth = 2   -- количество пробелов, вставляемых при шифтинге
opt.softtabstop = 2  -- ширина отступа
-- автоматически определять количество пробелов для отступа
opt.autoindent = true
opt.smartindent = true
opt.breakindent = true -- при переносе строки добавлять отступы

-- Поиск
opt.ignorecase = true
opt.smartcase = true
opt.incsearch = true
opt.hlsearch = true
opt.wrapscan = true
opt.inccommand = "split"

-- Файлы и буферы
-- В последнем nvim вроде всегда такое поведение
-- opt.hidden = true
-- Используем системный буфер для копирования текста
-- Требует наличия xclip или wl-copy!
opt.clipboard = "unnamedplus"
-- Бекапы, файлы подкачки, undo- и shada-файлы хранятся в ~/.local/state/nvim
-- Отключаем создание бекапов и файлов подкачки
opt.swapfile = false
opt.backup = false
opt.writebackup = false
opt.autoread = true -- Обновлять буфер при изменении файла извне
-- Делаем возможной отмену изменений уже после закрытия файла
opt.undofile = true
opt.undolevels = 1000
opt.shada = "!,'1000,<50,s10,h" -- ограничения для shada-файлов (от share data)
opt.confirm = true

-- Разделение окон
opt.splitbelow = true
opt.splitright = true

-- Мышь и работа с текстом
opt.mouse = "a"
opt.mousemoveevent = true
-- Выделение текста стрелками с зажатым Shift
opt.keymodel = "startsel,stopsel"
-- При переключении системной раскладки перестают работать привязки клавиш.
-- В vim можно включить встроенную русскую раскладку с переключением по Ctrl-6
opt.keymap = "russian-jcukenwin"
-- Делаем раскладкой по умолчанию английскую
opt.iminsert = 0
opt.imsearch = 0
-- Привычное перемещение курсора
opt.whichwrap:append("hl<>[]")

-- Автодополнение
opt.wildmenu = true
opt.wildmode = "longest:full,full"
-- Можно исключить из дополнения определенные типы файлов, имеющие бинарный формат
opt.wildignore:append { '*.o', '*.obj', '*.py[co]', '__pycache__/*', '*.so', '*.zip', '*.rar', '*.tar.*', '*.gz', '*.docx', '*.xlsx', '*.pdf', '*.jpg', '*.jpeg', '*.gif', '*.png' }
opt.completeopt = "menuone,noselect,noinsert"
opt.pumheight = 10 -- высота всплывающего меню с вариантами для дополнения

-- Таймауйты
opt.timeoutlen = 500
opt.updatetime = 250

-- spell
opt.spell = false
opt.spelllang = { "ru", "en" }

local spell_dir = config_dir .. '/spell'
if vim.fn.isdirectory(spell_dir) == 0 then
  vim.fn.mkdir(spell_dir, 'p')
end

Тут хранятся пользовательские сочетания клавиш, некоторые из них задаются странным образом — внутри функции:

lua/configs/keymaps.lua

local map = require("utils").map

map('n', '<C-a>', 'ggVG', "Select all text")
map('i', '<C-a>', '<Esc>ggVG', "Select all text")

map('n', '<C-q>', '<cmd>q<CR>', "Close current window")
map('n', '<C-x>', '<cmd>bd<CR>', "Delete current buffer")
-- В Neovim это сочетание уже используется
-- map('', '<C-s>', '<cmd>w<CR>', "Save file")
map('n', '<leader>w', '<cmd>w<CR>', "Save file")

-- Отступы привычнее добавлять с помощью Tab
map('n', '<Tab>', '>>_', "Increase indent")
map('n', '<S-Tab>', '<<_', "Decrease indent")
map('i', '<S-Tab>', '<C-D>', "Decrease indent")
map('v', '<Tab>', '>gv', "Increase indent")
map('v', '<S-Tab>', '<gv', "Decrease indent")

map('n', '<Esc>', '<cmd>nohlsearch<CR><Esc>', "Clear search highlight")

map('n', '<C-Up>', '<cmd>bp<CR>', "Previous buffer")
map('n', '<C-Down>', '<cmd>bn<CR>', "Next buffer")

map('n', '<C-h>', '<C-w>h', "Focus left window")
map('n', '<C-j>', '<C-w>j', "Focus lower window")
map('n', '<C-k>', '<C-w>k', "Focus upper window")
map('n', '<C-l>', '<C-w>l', "Focus right window")

map('n', '<A-h>', '<C-w>H', "Move window left")
map('n', '<A-j>', '<C-w>J', "Move window down")
map('n', '<A-k>', '<C-w>K', "Move window up")
map('n', '<A-l>', '<C-w>L', "Move window right")

map('n', '<A-Left>', '<cmd>vertical resize -2<CR>', "Decrease width")
map('n', '<A-Right>', '<cmd>vertical resize +2<CR>', "Increase width")
map('n', '<A-Down>', '<cmd>resize -2<CR>', "Decrease height")
map('n', '<A-Up>', '<cmd>resize +2<CR>', "Increase height")

map('v', "J", ":m '>+1<CR>gv=gv", "Shift visual selected line down")
map('v', "K", ":m '<-2<CR>gv=gv", "Shift visual selected line up")

map('n', '<leader>h', '<cmd>split<CR>', "Horizontal split")
map('n', '<leader>v', '<cmd>vsplit<CR>', "Vertical split")

-- nvim автоматически добавляет переменную $MYVIMRC, ее не нужно добавлять
map('n', '<leader>ev', '<cmd>edit $MYVIMRC<CR>', "Edit vim config")
map('n', '<leader>sv', '<cmd>so $MYVIMRC<CR>', "Reload vim config")

-- F3-F11 лучше оставить для дебаггера
map('', '<F2>', ":setlocal spell!<cr>", "Toggle spell check")

map("n", "<leader>ff", "<cmd>Telescope find_files<cr>", "find file")
map("n", "<leader>fg", "<cmd>Telescope live_grep<cr>", "Find [using] grep")
map("n", "<leader>fb", "<cmd>Telescope buffers<cr>", "find buffer")

-- Можно использовать любое из сочетаний с Ctrl+T/N/P, так как на них навешен бесполезный функционал
map("n", "<C-t>", "<cmd>NvimTreeToggle<CR>", "Toggle nvim tree")

-- Можно через telescope тоже самое делать
local setup_lsp_keymaps = function(_, bufnr)
  map("n", "gd", vim.lsp.buf.definition, "Go to definition", bufnr)
  map("n", "gD", vim.lsp.buf.declaration, "Go to declaration", bufnr)
  map("n", "gi", vim.lsp.buf.implementation, "Go to implementation", bufnr)
  map("n", "gr", vim.lsp.buf.references, "List references", bufnr)
  map("n", "K", vim.lsp.buf.hover, "Hover documentation", bufnr)
  -- На эту клавишу в режиме редактирования по умолчанию уже задано это действие
  map("n", "<C-s>", vim.lsp.buf.signature_help, "Signature help", bufnr)
  map("n", "<leader>rn", vim.lsp.buf.rename, "Rename symbol", bufnr)
  map("n", "<leader>ca", vim.lsp.buf.code_action, "Code action", bufnr)
  map("n", "]d", vim.diagnostic.goto_next, "Next diagnostic", bufnr)
  map("n", "[d", vim.diagnostic.goto_prev, "Previous diagnostic", bufnr)
  map("n", "<leader>e", vim.diagnostic.open_float, "Show diagnostics", bufnr)
end

return {
  setup_lsp_keymaps = setup_lsp_keymaps
}

В lua/utils.lua я вынес вспомогательную функцию для задания сочетаний, она выше импортируется:

local M = {}

-- Универсальная функция для биндингов
function M.map(mode, keys, command, desc, bufnr)
  local opts = { desc = desc, silent = true }
  if bufnr then
    opts.buffer = bufnr
  end
  vim.keymap.set(mode, keys, command, opts)
end

return M

В Nvim LSP встроенный. Например, Ctrl-S по умолчанию в режиме редактирования вызывает справку. Так же нам не нужны никакие плагины для комментариев — для этого есть сочетание gc.


lua/configs/plugins.lua

local setup_lsp_keymaps = require("configs.keymaps").setup_lsp_keymaps

return {
  -- Файловый менеджер
  {
    "nvim-tree/nvim-tree.lua",
    dependencies = { "nvim-tree/nvim-web-devicons" },
    config = function()
      require("nvim-tree").setup({
        view = { width = 30 },
        filters = { dotfiles = false },
        git = { enable = true },
        actions = {
          open_file = {
            quit_on_open = true, -- закроет дерево после открытия файла
          },
        },
        -- авто-закрытие при последнем буфере
        -- только если включена эта настройка:
        hijack_netrw = true,
      })
    end,
  },

  -- Поиск по файлам
  {
    "nvim-telescope/telescope.nvim",
    dependencies = { "nvim-lua/plenary.nvim" },
    config = function()
      require('telescope').setup({
        -- Тут какие-то настройки
      })
      -- require('telescope').load_extension('fzf')
    end
  },

  -- tree-sitter используется для парсинга сходников
  -- :checkhealth nvim-treesitter
  {
    "nvim-treesitter/nvim-treesitter",
    build = ":TSUpdate",
    config = function()
      local configs = require("nvim-treesitter.configs")

      configs.setup({
        -- Парсеры для каждого языка нужно ставить отдельно
        ensure_installed = { "c", "lua", "vim", "vimdoc", "python", "go", "rust", "java", "javascript", "php", "vue", "html", "json", "toml", "yaml" },
        sync_install = false,
        auto_install = true,
        highlight = { enable = true },
        indent = { enable = true },
      })
    end
  },

  -- LSP и Автодополнение
  -- https://medium.com/@rishavinmngo/how-to-setup-lsp-in-neovim-1c3e5073bbd1
  {
    "hrsh7th/nvim-cmp",
    dependencies = {
      'neovim/nvim-lspconfig',
      'hrsh7th/cmp-nvim-lsp',
      'hrsh7th/cmp-buffer',
      'hrsh7th/cmp-path',
      'hrsh7th/cmp-cmdline'
    },
    config = function()
      local cmp = require('cmp')
      cmp.setup({
        sources = {
          { name = 'nvim_lsp' },
          { name = 'buffer' },
        },
        mapping = cmp.mapping.preset.insert({
          ['<C-b>'] = cmp.mapping.scroll_docs(4),
          ['<C-f>'] = cmp.mapping.scroll_docs(-4),
          ['<C-Space>'] = cmp.mapping.complete(),
          ['<C-e>'] = cmp.mapping.abort(),
          ['<CR>'] = cmp.mapping.confirm({ select = true }),
        })
      })

      local lspconfig = require('lspconfig')
      local capabilities = require("cmp_nvim_lsp").default_capabilities()

      -- npm install -g pyright
      -- sudo pacman -S gopls lua-language-server
      -- Mason может автоматически ставить зависимости
      -- Тут нужно вписать названия серверов, поддерживаемых nvim-lspconfig
      local servers = {
        'pyright',
        'gopls',
        'lua_ls',
      }

      for _, lsp in ipairs(servers) do
        lspconfig[lsp].setup {
          on_attach = setup_lsp_keymaps,
          capabilities = capabilities,
        }
      end
    end
  },

  -- Выделение отступов
  {
    "lukas-reineke/indent-blankline.nvim",
    config = function()
      require("ibl").setup()
    end
  },

  -- Нижняя строка статуса
  {
    "nvim-lualine/lualine.nvim",
    dependencies = 'nvim-tree/nvim-web-devicons',
    config = function()
      require('lualine').setup({
        sections = {
          lualine_x = {
            -- Добавим отображение раскладки
            {
              function()
                if vim.opt.iminsert:get() > 0 and vim.b.keymap_name then
                  return '⌨ ' .. vim.b.keymap_name
                end
                return ''
              end,
              cond = function() -- Показывать только если раскладка активна
                return vim.opt.iminsert:get() > 0 and vim.b.keymap_name ~= nil
              end,
            },
            'encoding',
            'fileformat',
            'filetype',
          }
        }
      })
    end
  },

  -- Верхняя строка статуса (заменяет табы)
  {
    'akinsho/bufferline.nvim',
    version = "*",
    dependencies = 'nvim-tree/nvim-web-devicons',
    config = function()
      require("bufferline").setup {
        options = {
          mode = "buffers",
          separator_style = "slant"
        }
      }
    end,
  },

  -- Тема
  {
    "folke/tokyonight.nvim",
    lazy = false,
    priority = 1000,
    opts = {},
    config = function()
      require("tokyonight").setup({
        -- Какие-то настройки
      })
      vim.cmd [[colorscheme tokyonight-storm]]
    end
  },

  -- Фиксим прозрачность
  {
    "xiyaowong/transparent.nvim",
    config = function()
      require('transparent').setup({
        extra_groups = {
          'NormalFloat', -- plugins which have float panel such as Lazy, Mason, LspInfo
        },
        exclude_groups = { 'CursorLine' },
      })
    end
  }
}

В plugins.lua перечислены плагины. Обратите внимание, что внутри почти каждого блока объявлена функция config. Она вызывается после установки и/или после готовности плагина к использованию, после чего в большинстве случаев нужно импортировать плагин и вызвать функцию setup, объявленную внутри, в которую передаётся ассоциативный массив с настройками.

В Neovim парсинг файлов с исходниками происходит через плагины для Tree-sitter (является зависимостью редактора). С помощью него строится синтаксическое дерево, которое используется для автодополнения, анализа кода, а также опционально для подсветки синтаксиса. Пакет с одноимённым названием, который мы устанавливаем, лишь содержит список парсеров, доступных для скачивания и установки, а также может их скачивать.

nvim-lspconfig содержит названия LSP-серверов и их стандартные настройки, которые можно переопределить. Языковые сервера можно устанавливать автоматически с помощью Mason. Их установка с помощью последнего позволяет изолировать окружение для разработки. Однако, если у вас все эти средства уже установлены, например, так как вами используется VSCode, то Mason будет лишним.

Помимо плагина для комментариев, не нужен и плагин для editorconfig, так как последний поддерживается из коробки.


В lua/configs/autocmds.lua находятся команды, которые активируются при определённых событиях:

-- Автоперечтение файла при изменении
vim.api.nvim_create_autocmd({ "FocusGained", "BufEnter", "CursorHold" }, {
  command = "checktime",
})

-- Форматирование при сохранении
vim.api.nvim_create_autocmd("BufWritePre", {
  callback = function()
    vim.lsp.buf.format {
      async = false
    }
  end,
})

-- Показывать всплывающие окна с ошибками при наведении курсора
vim.api.nvim_create_autocmd("CursorHold", {
  callback = function()
    vim.diagnostic.open_float(nil, { focusable = false })
  end,
})

Заключение

Получившийся конфиг полностью готов к использованию. Вы можете ознакомиться с ним, посетив мой репозиторий на Github.

А так же себе его развернуть:

git clone https://github.com/s3rgeym/nvim-config ~/.config/nvim 

Nvim, хотя и является редактором для терминала, но так же для него существуют т. н. фронтенды. Из популярных — Neovim-qt и Neovide. Последний имеет поддержку лигатур, GPU-ускорение и анимации для прокрутки текста и курсора.

Альтернативой Nvim являются Kakoune и Helix. Если первый представляет собой скорее Vim здорового человека (или криворука, который не в состоянии настроить первый — решайте сами), то Helix можно рассматривать как преднастроенный Nvim, где вместо ковыряния Lua-лапши можно редактировать TOML. Для кого-то это будет огромным плюсом, но, как по мне, это накладывает гигантские ограничения и противоречит самой концепции программируемого редактора. Хотя я их и не советую использовать, но об их существовании стоит упомянуть, так как они скорее служат для того, чтобы понять, подходит ли для тебя Vim/Nvim.

 , , ,

rtxtxtrx
()

Развёртывание и базовая настройка LibreChat

Статьи — Desktop

Нельзя не заметить, что небольшое, но очень дружное сообщество LORa феноменально мало пишет о достижениях и возможностях «народного хозяйства» с очень плодородных полей открытых LLM (large language model), и всего опенсорсного с этим связанного. Сегодня я расскажу вам о LibreChat.

Что такое LibreChat

LibreChat – это платформа с открытым исходным кодом, во многом копирующая интерфейс ChatGPT, и взаимодействующая с различными LLM (как открытими локальными, так и большими коммерческими, через api). По сути, это клиентское приложение, которое позволяет общаться с LLM, предоставляя при этом множество дополнительных функций.

Ключевые возможности LibreChat:

  1. Поддержка множества ИИ-моделей: LibreChat позволяет подключаться и использовать модели от разных поставщиков, включая OpenAI, Azure, Bing AI, Google, Anthropic, DeepSeek, а также локально запущенные модели через Ollama и LM Studio.
  2. Расширенные возможности управления диалогами: LibreChat предлагает инструменты для редактирования сообщений, повторной отправки запросов, поиска по истории сообщений, создания «веток» (forking) диалогов для исследования разных путей ответа, а также импорта и экспорта бесед.
  3. Настройка и Пресеты: Пользователи могут создавать и сохранять пользовательские «пресеты» (presets) — предопределенные конфигурации для разных моделей ИИ. Это позволяет быстро переключаться между настроенными профилями, например, с разными системными сообщениями, параметрами температуры или моделями.
  4. Поддержка Плагинов: LibreChat интегрирует поддержку плагинов, например таких как плагины для поиска в интернете и генерации изображений с помощью Stable Diffusion.
  5. Многопользовательский Режим: Существует возможность настройки LibreChat для использования несколькими пользователями с аутентификацией и управлением учетными записями.
  6. Интерфейс и Удобство: Цель проекта — предоставить улучшенный, интуитивно понятный и настраиваемый интерфейс для работы с ИИ, включая такие детали, как отображение количества токенов и потенциальной стоимости запросов.

Иными словами, основное назначение LibreChat – предоставить централизованную и гибкую платформу для взаимодействия с различными технологиями искусственного интеллекта.

В этой статья я покажу, как развернуть LibreChat на локальной машине, через docker-compose, как его просто настроить в связке с коммерческими LLM, как его настроить для работы с Reverse proxy и как его настроить в связке с локальными LLM.


Локальное развёртывание LibreChat

Локальное развёртывание через docker-compose я выбрал неслучайно, оно ничем не проще облачного, зато так вы сможете посмотреть на что способен LibreChat, если вы будете запускать его вместе с очень полезными локальными инструментами, на запуск которых простой сервер не способен. При этом настройки локального и облачного развёртывания отличаются не сильно, но всё же позже я вскользь упомяну разницу между ними. Если вас не устраивает docker compose, то LibreChat даёт возможность развернуть локально с помощью npm или в облаке, через Hugging Face или Railway.

Установка LibreChat

Раз уж я выбрал docker-compose, то предполагается, что он уже у вас установлен вместе с docker/podman, под podman желательно внести изменения в конфиг docker-compose.override.yml и поменять многие bind mounts на named volumes.

$ git clone https://github.com/danny-avila/LibreChat.git
$ cd LibreChat
$ cp .env.example .env
$ docker-compose up -d
or
$ podman compose up -d

Для обновления контейнера:

$ docker-compose down
$ git pull
$ docker-compose pull
$ docker compose up -d

Если podman, то аналогично:

$ podman compose down
$ git pull
$ podman compose pull
$ podman compose up -d

Здесь одно из основных отличий с облачным развёртыванием. В облаке вместо docker-compose.yml, нужно запускать deploy-compose.yml, а обновление будет происходить по скрипту npm run update:deployed. Второе различие связано с тем, что скорее всего на сервере вы будете запускать docker/podman от рута. Локально же перед вами открывается необходимость запускать контейнеры в rootless режиме. По моему мнению podman подходит для этого намного лучше, но эта тема не этой статьи.

Чтобы посмотреть список запущенных контейнеров и их логи:

$ docker ps
$ docker logs ваш контейнер

podman:

$ podman ps
$ podman logs ваш контейнер

Теперь идёте на http://localhost:3080/ и видите экран входа. Регистрируйтесь: придумайте себе логин, пароль, напишите какую-то почту. Регистрация просто наследуется из облачной версии, а первым по умолчанию создается учётная запись админа. Можете входить в учётную запись, первичная установка закончена, но в таком виде LibreChat бесполезен. Самое время настроить доступ к LLM.

Настройка LibreChat

Теперь важное замечание. Ваш LibreChat работает, вы можете любоваться его ChatGPT-подобным интерфейсом, но во всём этом нет толка, если вы не обладаете ключами от коммерческих моделей или не имеете достаточно мощного компьютера, чтобы развернуть на нём свои родные локальные.

Часть первая: бесхитростная

Если вы обладаете коммерческими ключами от LLM, а ваша локальная машина постоянно находится не в России или делает вид, что там не находится, то подключенине к LLM будет очень скучным. Если это не так, и ваши ключи не от DeepSeek, то переходите сразу ко второй части. Дело в том, что большинство провайдеров коммерческих моделей определяют страну из которой сделан запрос и блокируют его, если эта страна их не устраивает. DeepSeek исключение, он предоставляет доступ в Россию напрямую.

Все необходимые настройки вы легко проведёте прямо из графического интерфейса. В нём вы найдёте интересующую вас LLM, и внесёте в специальное поле свои api ключи. Всё, настройка завершена.

Если этот путь не для вас, то открывайте .env, раскомментируйте эти строки и записывайте все свои ключи:

DEEPSEEK_API_KEY="ВАШ КЛЮЧ"
ANTHROPIC_API_KEY="ВАШ КЛЮЧ"
MISTRAL_API_KEY="ВАШ КЛЮЧ"
OPENROUTER_KEY="ВАШ КЛЮЧ"
GOOGLE_KEY="ВАШ КЛЮЧ"
OPENAI_API_KEY="ВАШ КЛЮЧ"
Итд

Дополнительно в .env можете указать конкретные модели, с которыми хотите иметь дело:

OPENAI_MODELS="Список LLM"
GOOGLE_MODELS="Список LLM"
Итд

В завершении вносите туда адрес RAG (об этом подробнее чуть позже):

RAG_PORT=8000
RAG_API_URL=http://host.docker.internal:8000

Если у вас есть api от OpenAI, то на этом всё, а если у вас api от других LLM, то дополнительно вносите:

EMBEDDINGS_PROVIDER="google" # ИЛИ КТО УГОДНО ПОПУЛЯРНЫЙ

Это будет работать, если у вас кто-то популярный типо google, а так же ранее был внесён ключ в соответствующую секцию, в вашем случае в GOOGLE_KEY

Работая с файлом .env, не забывайте проверить, что он добавлен в ваш .gitignore, если вы, конечно, не коммунист!

Перезагружайте docker-compose, настройка завершена.

Часть вторая: с хитрицой

Если вы обладаете коммерческими ключами от LLM, а ваша локальная машина постоянно находится в России и не делает вид, что там не находится, и ваши ключи не от DeepSeek, то подключенине к LLM будет менее скучным. Для этого вам понадобится vps другой страны.

Собственно тогда вы можете и LibreChat развернуть прямо на сервере, вскользь я упоминал, что нужно делать. Но в этом случае вам будет неудобно взаимодействовать с локальными LLM и не только. Чтобы вы могли достучаться до большинства коммерческих LLM, вам придётся настроить Reverse proxy, в вашем случае это будут (именно будУТ) nginx и LiteLLM.


Что такое LiteLLM

С nginx понятно зачем он нужен, но что такое LiteLLM? LiteLLM — это Python-библиотека, которая предоставляет единый, стандартизированный способ отправки запросов к api множества различных LLM провайдеров, в том числе и локальных. Вместо того чтобы изучать и реализовывать специфические детали api каждого провайдера, вы можете использовать единый вызов функции LiteLLM.

Основные возможности LiteLLM:

  1. Унификация api: Это главная задача. У разных провайдеров разные названия параметров, форматы запросов и ответов, методы аутентификации. LiteLLM скрывает эту сложность за единым интерфейсом (обычно это функция litellm.completion() или litellm.acompletion() для асинхронных вызовов). Вы просто указываете, какую модель использовать, а LiteLLM сам формирует правильный запрос к нужному api.

  2. Простота переключения между моделями/провайдерами: Благодаря единому интерфейсу, переход с одной модели на другую (даже от другого провайдера) часто требует изменения всего одной строки кода — имени модели.

  3. Поддержка множества LLM: LiteLLM поддерживает десятки api всех популярных LLM провайдеров. А локальные LLM используют те же api, что и коммерческие.

  4. Функциональность прокси-сервера: это очень важная для вас вещь. LiteLLM можно запустить как отдельный прокси-сервер. В этом режиме он принимает запросы в стандартном формате (часто совместимом с OpenAI api) и перенаправляет их к настроенным LLM провайдерам. Это дает дополнительные возможности:

    • Обход блокировок по geoip: Запрос к LLM провайдеру будет отправелн с вашего иностранного сервера.
    • Централизованное управление ключами api: Не нужно хранить ключи в каждом приложении.
    • Балансировка нагрузки: Распределение запросов между несколькими экземплярами моделей или провайдерами.
    • Резервные модели (Fallbacks): Автоматическое переключение на другую модель, если основная недоступна или выдает ошибку.
    • Кэширование: Сохранение ответов на одинаковые запросы для экономии средств и ускорения.
    • Ограничение частоты запросов (Rate Limiting): Контроль нагрузки на api.
    • Отслеживание затрат: Централизованный учет стоимости вызовов разных моделей.
    • Логирование и мониторинг: Единое место для сбора логов и метрик использования LLM.

1. Настройка nginx

Теоретически, чтобы реализовать работу Reverse proxy для запросов к api LLM, можно обойтись только одним nginx. Но тогда непременно придётся городить велосипеды в попытках обратиться к разным LLM, поэтому вам и нужна связка из nginx и LiteLLM.

Предполагается, что вы знаете, как настривать nginx и можете адаптировать свой конфиг под работу вместе с LiteLLM. Если нет, то вкратце: ваш сервер должен быть доступен по https, в самом простом случае для этого вы должны установить nginx и certbot. Ввести эту команду certbot --nginx -d ВАШ DNS или IP.

Пример простейщего конфига /etc/nginx/sites-available/librechat-proxy:

# /etc/nginx/sites-available/librechat-proxy

server {
    listen 80;
    server_name ВАШ DNS или IP;

    # Letsencrypt challenge handling (если используете certbot --nginx)
    location ~ /.well-known/acme-challenge/ {
        allow all;
        root /var/www/html; # Укажите путь, используемый Certbot
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl http2;
    server_name ВАШ DNS или IP;

    ssl_certificate /etc/letsencrypt/live/ВАШ DNS или IP/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/ВАШ DNS или IP/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    # Дополнительные заголовки безопасности (опционально)
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Content-Type-Options nosniff always;
    add_header X-Frame-Options SAMEORIGIN always;
    # add_header Content-Security-Policy "default-src 'self'"; # Будьте осторожны с CSP

    location / {
        # Проксируем на LiteLLM, который слушает на порту 4000 на localhost хоста
        proxy_pass http://127.0.0.1:4000;

        # Стандартные заголовки для проксирования
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Настройки для поддержки потоковой передачи (важно для LLM)
        proxy_http_version 1.1;         # Обязательно для keepalive и chunked transfer encoding
        proxy_set_header Connection ""; # Сбрасываем заголовок Connection для апстрима
        proxy_buffering off;            # Отключаем буферизацию ответа от бэкенда
        proxy_cache off;                # Отключаем кеширование Nginx

        # Иногда этот заголовок может помочь с буферизацией в некоторых клиентах/серверах
        proxy_set_header X-Accel-Buffering no;

        # Увеличение таймаутов (важно для долгих запросов LLM)
        proxy_connect_timeout 300s; # Время на установку соединения с LiteLLM
        proxy_send_timeout 300s;    # Время на передачу запроса LiteLLM
        proxy_read_timeout 300s;    # Время на чтение ответа от LiteLLM (самый важный)
        send_timeout 300s;          # Время на передачу ответа клиенту

        # Максимальный размер тела запроса (если нужно передавать большие контексты)
        client_max_body_size 50m; # Установите разумный лимит (50m может быть избыточно)
    }

    # Добавьте location для favicon и статики Nginx, чтобы они не шли в LiteLLM (опционально)
    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    # Добавьте обработку ошибок (опционально)
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
        root /usr/share/nginx/html; # Или другой путь к стандартным страницам ошибок
    }
}

Проверьте:

nginx -t

Вы должны получить syntax is ok и test is successful. Если это так перезагружайте nginx systemctl restart nginx. Его настройка завершена.

2. Установка LiteLLM

Для установки LiteLLM вам снова понадобится docker-compose и docker/podman:

git clone https://github.com/BerriAI/litellm.git
cd litellm
cp .env.example .env
touch config.yaml

Дежурное напоминание, работая с файлом .env, не забывайте проверить, что он добавлен в ваш .gitignore, если вы, конечно, не коммунист!

3. Настройка docker-compose.override.yml

Внимательно прочтите ваш docker-compose.yml. В нём описана работа трёх контейнеров, работающих вместе: litellm_litellm_1основной сервис LiteLLM Proxy, litellm_db_1база данных PostgreSQL и litellm_prometheus_1система мониторинга Prometheus. В принципе вы можете переписать свой docker-compose.yml так, чтобы работал только первый контейнер, для простейшего обхода ограничений и работы с LLM этого будет достаточно. Другие контейнеры нужны для реализации всего функционала, описанного где-то выше.

После создаёте docker-compose.override.yml в него записываете:

# docker-compose.override.yml
version: "3.11" # Используйте ту же версию, что и в основном файле

services:
  litellm:
    # Подключаете ваш кастомный конфиг внутрь контейнера
    volumes:
      - ./config.yaml:/app/config.yaml:ro # :ro - монтируйте только для чтения

    # Указываете LiteLLM использовать ваш конфиг при запуске
    command:
      - "--config"
      - "/app/config.yaml"
      # Если нужно добавить другие флаги, например, для включения дебага:
      # - "--debug"

    # Убедитесь, что переменные из .env файла загружаются (это уже есть в базовом конфиге, но для ясности можно продублировать)
    env_file:
      - .env

4. Настройка .env

Вам необходимо сгенерировать свой LITELLM_MASTER_KEY. Сделать это можно например, с помощью: openssl rand -base64 32, или как вам будет удобно. Этот ключ нужен для управления LITELLM. Вы создадите с помощью него «фейковые» ключи для LibreChat. Ключ обязательно должен начинаться с sk-.

# Development Configs
LITELLM_MASTER_KEY = "sk-СГЕНЕРИРОВАННЫЙ КЛЮЧ"

# ЭТО НАСТРОЙКИ ИЗ .env.example, ЕСЛИ ВЫ ИСПОЛЬЗУЕТЕ ОРИГИНАЛЬНУЮ СВЯЗКУ ИЗ ТРЁХ КОНТЕНЕРОВ
DATABASE_URL = "postgresql://llmproxy:dbpassword9090@db:5432/litellm"
STORE_MODEL_IN_DB = "True"

# ДАЛЬШЕ БУДУТ КЛЮЧИ ВАШИХ МОДЕЛЕЙ, КАК ПРИМЕР ТУТ БУДУТ КЛЮЧИ ОТ Google и OpenAI
# КЛЮЧЕЙ МОЖЕТ БЫТЬ СКОЛЬКО УГОДНО

# Google
GEMINI_API_KEY_1 = "Google_КЛЮЧ_1"
GEMINI_API_KEY_2 = "Google_КЛЮЧ_2"

# OpenAI
OpenAI_API_KEY = "OpenAI_КЛЮЧ"

5. Настройка config.yaml

В качестве примера приведу настройку config.yaml:

# config.yaml

model_list: # Определить модели и их группы
  - model_name: gemini-2.5-pro-exp-03-25 # Имя может быть любым
    litellm_params:
      model: gemini/gemini-2.5-pro-exp-03-25 # Реальная модель Gemini
      api_key: os.environ/GEMINI_API_KEY_1

  - model_name: gemini-2.5-pro-exp-03-25 # То же имя для балансировки
    litellm_params:
      model: gemini/gemini-2.5-pro-exp-03-25
      api_key: os.environ/GEMINI_API_KEY_2

  - model_name: gemini-2.0-flash-exp-image-generation # Группа для Flash модели
    litellm_params:
      model: gemini/gemini-2.0-flash-exp-image-generation
      api_key: os.environ/GEMINI_API_KEY_1

  - model_name: gemini-2.0-flash-exp-image-generation # То же имя для балансировки
    litellm_params:
      model: gemini/gemini-2.0-flash-exp-image-generation
      api_key: os.environ/GEMINI_API_KEY_2

  - model_name: gpt-4o
    litellm_params:
      model: openai/gpt-4o
      api_key: os.environ/OPENAI_API_KEY

  - model_name: o3-mini
    litellm_params:
      model: openai/o3-mini
      api_key: os.environ/OPENAI_API_KEY

router_settings:
  routing_strategy: simple-shuffle # Балансировка

database_settings:
  database_url: os.environ/DATABASE_URL

litellm_settings: # Основные настройки движка LiteLLM
  drop_params: True

general_settings: # Общие настройки прокси-сервера
  master_key: os.environ/LITELLM_MASTER_KEY
  litellm_proxy_admin_ui:
    enable: True
  completion_log_in_db: True
  disable_telemetry: True

Я не случайно выбрал ситуацию, в которой у вас оказалось два ключа от одной LLM (может быть сколько угодно). В этом случае вы можете настроить балансировку. Для этого необходимо указать один и тот же model_name и model, но разные api_key. А ниже в секции router_settings выбрать стратегию simple-shuffle. Перечисление возможностьей и отличий каждой стратегии уходит далеко за пределы этой статьи. Скажу лишь, что их всего четыре, это: «simple-shuffle», «least-busy», «usage-based-routing»,«latency-based-routing».

simple-shuffle случайным образом выбирает одно из доступных развертываний (т.е. один из ваших api-ключей). Это стратегия по умолчанию, которая хорошо подходит для малого количества ключей с одинаковыми лимитами, для одной модели.

Если у вас только по одному уникальному ключу. То секция router_settings не нужна!

Самое время запустить LiteLLM и посмотреть логи. В нём не должно быть никаких критических ошибок:

docker-compose up -d
docker logs -f litellm_litellm_1

Осталось сгенерировать «фейковые» ключи для LibreChat:

curl --location 'https://ВАШ DNS ИЛИ IP' \
--header 'Authorization: Bearer <СЮДА ПИСАТЬ ВАШ СГЕНЕРИРОВАННЫЙ ДЛЯ .env LITELLM_MASTER_KEY>' \
--header 'Content-Type: application/json' \
--data '{
    "models": ["gemini-2.5-pro-exp-03-25", "gemini-2.0-flash-exp-image-generation", "gpt-4o", "o3-mini"],
    "aliases": {},
    "max_budget_usd": null,
    "duration": null,
    "user_id": "librechat_my_virt_key",
    "metadata": {"description": "Key for my LibreChat"}
}'

LiteLLM вернет JSON-ответ, содержащий сгенерированный ключ в секции token, он будет начинаться с sk-. Сохраните этот ключ! Настройки nginx и LiteLLM завершены.


Возвращение к LibreChat

Ступайте обратно на свою локальную машину, на которой установлен и работает LibreChat. Идите в его каталог и создайте docker-compose.override.yml:

$ cp docker-compose.override.yml.example docker-compose.override.yml
$ touch librechat.yaml
$ vi docker-compose.override.yml

В нём нужно раскомментировать строки, чтобы получилось так:

services:
  api:
    volumes:
      - type: bind
        source: ./librechat.yaml
        target: /app/librechat.yaml

Файл librechat.yaml является основным конфигурационным файлом для приложения LibreChat. Он позволяет вам настраивать различные аспекты работы приложения централизованно, переопределяя стандартные настройки и значения, заданные в переменных окружения в .env.В нём вы можете указать ваши ендпойнты. То есть вы можете указать, как адреса и ключи коммерческих LLM, что вы настроивали в LiteLLM, так и локальных LLM, которые вам ещё только предстоит настроить.

Открывайте librechat.yaml и вносите туда модели, которые проксировали в LiteLLM :

#librechat.yaml
version: 6.6.6
cache: true
endpoints:
  agents:
    capabilities: ["execute_code", "file_search", "actions", "tools"]
  custom:
    - name: "OpenAI"
      iconURL: "openAI"
      apiKey: "${MYAPIKEY}"
      baseURL: "АДРЕС ВАШЕГО СЕРВЕРА С НАСТРОЕННЫМ LITELLM и NGINX"
      models:
        default: ["o3-mini", "gpt-4.5"] # Тут записаны openai модели из model_name config.yaml LiteLLM
        fetch: false
      titleConvo: true
      titleModel: "current_model"
      summarize: true
      summaryModel: "current_model"
      forcePrompt: false
    # modelDisplayLabel можно внести любое имя. Оно будет отображаться в чате.
      modelDisplayLabel: "gpt"
      dropParams: ["temperature"]
    - name: "Google"
      iconURL: "google"
      plugins: true
      apiKey: "${MYAPIKEY}"
      baseURL: "АДРЕС ВАШЕГО СЕРВЕРА С НАСТРОЕННЫМ LITELLM и NGINX"
      models:
        # Тут записаны google модели из model_name config.yaml LiteLLM
        default: ["gemini-2.5-pro-exp-03-25", "gemini-2.0-flash-exp-image-generation"]
        fetch: false
      titleConvo: true
      titleModel: "current_model"
      summarize: true
      summaryModel: "current_model"
      forcePrompt: false
      modelDisplayLabel: "gemini"
      dropParams: ["presence_penalty"]

Теперь открываете .env и вносите туда «фейковый» ключ, который вы создавали ранее в LiteLLM. Меняете настройки переменной ENDPOINTS, чтобы в LibreChat были доступны только агенты и ваши ендпойнты:

# ВОТ ЭТО НАДО ДОБАВИТЬ
MYAPIKEY="sk-ФЕЙКОВЫЙ КЛЮЧ"
# ВОТ ЭТО НАЙТИ И ИЗМЕНИТЬ
ENDPOINTS=custom,agents

УРА! вы закончили. Осталось только перезагрузить контейнеры, и после этого в LibreChat будут доступны ваши google и openai LLM (или те LLM, ключи от которых у вас есть):

$ docker-compose down
$ docker compose up -d

Всё? НЕТ, НЕ ВСЁ!


Часть третья: хитрая

До этого я говорил только о настройке и подключении коммерческих LLM, так же в первой части я упоминал загадочную настройку RAG. Самое время затронуть эти темы.

Да кто такая эта Ollama

Подробный разговор об Ollama это тема отдельной немалой статьи, поэтому большого перечисления возможностей, разнообразия моделей, системных требований к каждой модели и прочего тут не будет. Но раз я решил показать её простейшую интеграцию с LibreChat, то нужно дать хоть какое-то объяснение.

Ollama – это инструмент с открытым исходным кодом, который позволяет локально скачивать, настраивать и запускать LLM.

Ollama решает несколько важных задач и предлагает ряд преимуществ:

  • Простота: Она значительно упрощает сложный процесс установки и настройки LLM. Не нужно разбираться с зависимостями Python, версиями библиотек и т.д.
  • Локальный запуск:
  • Доступность: Делает мощные LLM доступными для разработчиков, исследователей и просто энтузиастов без необходимости платить за облачные api или иметь супермощные серверы (хотя для больших моделей всё-равно нужен мощный ПК).
  • Гибкость: Позволяет легко переключаться между разными моделями и пробовать их.
  • API для разработчиков: Ollama предоставляет локальный api, который позволяет интегрировать LLM в ваши собственные приложения.

Пока Ollama недоступна в большинтсве Linux-репозиториев, за исключением Arch, поэтому установка осуществляется так:

# если у вас arch
# без cuda
sudo pacman -S ollama
# c cuda
sudo pacman -S ollama-cuda
# для остальных
$ curl -fsSL https://ollama.com/install.sh | sh
# После установки
$ sudo systemctl daemon-reload
$ sudo systemctl enable --now ollama

Полезно будет продемонстрировать несколько примеров:

Пример 1: Скачивание и первый запуск модели

Допустим, вы хотите попробовать какую-то модель.

$ ollama pull "какая-то модель"
$ ollama run "какая-то модель"

Если вы сразу попытаетесь запустить модель, которой у вас нет,

$ ollama run "другая какая-то модель

то:

1.  Ollama проверит, скачана ли она у вас.
2.  Если нет, но модель с таким названием существует, она автоматически скачает ее.
3.  После скачивания модель запустится, и вы увидите приглашение для ввода текста.
  • Как использовать:
    • Просто начните вводить свой вопрос или текст прямо в терминале и нажмите Enter.
    • Модель сгенерирует ответ.
    • Чтобы выйти из чата с моделью, введите команду /bye и нажмите Enter или Ctrl+С Ctrl+D.

Пример 2: Запуск конкретной версии или размера модели

Модели часто имеют разные размеры (например, 8 миллиардов параметров - 8b). Меньшие модели быстрее, но тупее, большие – тупее поменьше.

# Скачать и запустить Llama 3 с 8 миллиардами параметров
$ ollama pull llama3:8b
$ ollama run llama3:8b

# Чтобы увидеть, какие модели уже есть на вашем компьютере:
$ ollama list

Пример 3: Удаление модели

$ ollama rm llama3:8b

Пример 4: Если вы хотите получить ответ на один конкретный вопрос и сразу выйти

$ ollama run llama3:8b "Зачем я пишу эту статью в 3 часа ночи?"

Пример 5: Использование API

Ollama также запускает локальный веб-сервер (обычно по адресу http://localhost:11434), к которому можно отправлять запросы. Это полезно для интеграции со своими скриптами или приложениями.

curl http://localhost:11434/api/generate -d '{
  "model": "llama3:8b",
  "prompt": "Что такое теодицея?",
  "stream": false
}'

Подключение локальных LLM

Ну хватит примеров, пора подключать локальные LLM и RAG.

Снова открываете librechat.yaml и добавляете свою модель:

  custom:
    - name: "Ollama"
    # api ключ для Ollama используется как плейсхолдер, но для LibreChat эта запись нужна
      apiKey: "ollama"
    # Базовый URL для api Ollama. По умолчанию используется http://localhost:11434
    # Тут по идее настройки docker/podman будут разниться, вам может потребоваться настроить ollama так, чтобы она слушала все интерфейсы на 11434 порту
    # Если Ollama работает в Docker, или вы настроили работу, как в совете выше, то:
      baseURL: "http://host.docker.internal:11434/v1/chat/completions"
      models:
        default: ["llama3:8b"]
        fetch: true
      titleConvo: true
      titleModel: "current_model"
      summarize: false
      summaryModel: "current_model"
      forcePrompt: false
      modelDisplayLabel: "Ollama"

Перезагружаете LibreChat, теперь вам доступна llama3. А значит вы можете идти изучать список последних локальных LLM и их системные требования. (Важно понять, что если у вас полумёртвый ноутбук или пк времён, когда зумеры ещё не родились, то вся практическая третья часть скорее всего не для вас).

nvidia со своими cuda раскрывается лучше всего, а если у неё ещё много vram, то перед вами открываются очень широкие возможности. Но даже отсутствие nvidia не приговор. Локальные LLM могут работать на очень разном железе:

  • AMD c ROCm (с помощью неё по идее вы сможете запускать cuda)
  • CPU (вам понадобится много ram)

Системные требования

Привожу по просьбе @hobbit системные требования нескольких актуальных на данный момент локальных LLM:

Категория: Менее 4 млрд. параметров (<4B) (Ориентировочно: 2-8 Гб VRAM)

  • Llama-3.1-Minitron-4B

    • Уменьшенная и дообученная версия модели Llama 3.1 8B. Предназначена для работы на системах с сильно ограниченными ресурсами.
  • Phi-3-mini

    • Модель от Microsoft, демонстрирующая высокую производительность для своего размера (3.8B параметров). Подходит для задач, требующих сообразительности при минимальных ресурсах.
  • Gemma-2B

    • Наименьшая модель в линейке Gemma от Google. Разработана для запуска на устройствах с низкими вычислительными мощностями. Пользователи отмечают её конкурентоспособность даже по сравнению с некоторыми моделями 7-8B,
  • Qwen2-1.5B

    • Базовая модель из семейства Qwen2 с минимальным количеством параметров. Способна генерировать связный текст, являясь начальным уровнем для работы с LLM на очень слабых системах.

Категория: 7-12 млрд. параметров (7+B) (Ориентировочно: 8-16 Гб VRAM)

  • Mistral-Nemo-Instruct-2407-12B

    • Новая модель от Mistral AI (12B параметров). Заявлена как конкурент Gemma 27B по уровню сообразительности. Отличается высокой скоростью генерации и поддержкой контекстного окна 128k. Хорошо понимает инструкции и генерирует на русском языке. Из недостатков отмечаются повторяющиеся циклы («лупы») и наличие скрытых ограничений (bias). Ожидаются дообученные версии для устранения этих проблем.
  • Llama-3.1-8В

    • Обновленная версия Llama 3 (8B параметров), позиционируемая как часть модели 405B. По бенчмаркам разработчиков превосходит Gemma 9B, Mistral 7B и GPT-3.5 Turbo. Первоначальные тесты указывают на некоторую сухость стиля и наличие ограничений/bias, которые, по сообщениям, относительно легко обходятся. Поддерживает контекст 128k.
  • Gemma 2-9B

    • Обновленная версия Gemma от Google (9B параметров).
  • Qwen2-7B-Instruct

    • Модель из семейства Qwen2 (7B параметров), обученная следованию инструкциям.
  • Llama-3-8В

    • Базовая модель Llama 3 (8B параметров), оказавшая значительное влияние на сегмент малых LLM. Обладает неплохим уровнем русского языка, но может проявлять невнимательность к деталям инструкций и уступает моделям 30B+

Категория: ~30 млрд. параметров (~30B) (Ориентировочно: 12-36 Гб VRAM)

  • Mistral-Small-Instruct-2409 - 22B

    • Новая модель от Mistral AI среднего размера (22B параметров). Считается одной из лучших в своем классе на момент выхода.
  • Gemma 2-27B

    • Новая модель от Google (27B параметров). Отличается высоким качеством генерации на русском языке и производительностью, сравнимой с некоторыми 70B моделями. Однако имеет тенденцию к скрытым ограничениям/bias, которые могут проявляться со временем.
  • Qwen2.5-32B

    • Обновленная версия китайской модели Qwen (32B параметров). Поддерживает контекст 128k, показывает хорошие результаты в задачах кодирования.

Категория: ~70 млрд. параметров (~70B) (Ориентировочно: 24-64 Гб VRAM)

  • Llama-3.3-70В

    • Новейшее обновление Llama 3 в размере 70B. Заявлено, что производительность находится на уровне Llama-3.1-405B, однако по пользовательским отчетам уступает Qwen2.5-72B. Поддерживает контекстное окно 128k.
  • Llama-3.1-70В

    • Обновление Llama 3 (70B параметров), считается частью (обрезанной версией) 405B модели. По бенчмаркам разработчиков превосходит GPT-3.5 Turbo. Поддерживает контекст 128k.
  • Qwen2-72B

    • Китайская модель (72B параметров), позиционируемая как превосходящая Llama 3 70B, присутствует цензура. Однако существуют мнения, что она уступает Llama 3 в практических задачах и медленнее из-за особенностей механизма внимания. Есть подозрения на завышение результатов в бенчмарках и наличие сильных ограничений/bias.

Категория: >100 млрд. параметров (100B+) (Ориентировочно: >64 Гб VRAM, требует мощных конфигураций)

  • Mistral-Large-123B

    • Обновление линейки Mistral (123B параметров). Скорость генерации приемлемая (~10-12 токенов/сек на соответствующем оборудовании). Очень сильная локальная модель, отмечается её превосходство над Llama 70B и её дообученными версиями.
  • Meta-Llama-3.1-405B

    • Самая крупная модель 3 поколения от Meta (405B параметров). Является базовой версией, из которой получены модели 70B и 8B. Заявленный уровень производительности — GPT-4 / Claude 3 Sonnet (что оспаривается пользователями). Первые тесты указывают на наличие сильных ограничений/bias («соя»), но они обходятся. Поддерживает контекстное окно 128k. Требует очень мощного оборудования.

На момент написания этой статьи вышли новые gemma 3 и Llama-4, а так же дистиляты DeepSeek разных вариаций. Предоставление подробной информации об их различиях, системных требований и прочего далеко уходит за грань этой статьи. И писать такую статью я не собираюсь (скорость выхода и развития новых моделей так высока, что это бессмысленно).


ХУ ИЗ RAG

RAG (Retrieval-Augmented Generation, что можно перевести как «Генерация, дополненная поиском») – это подход, который объединяет возможности большой языковой модели (LLM) по генерации текста с внешней системой поиска (retrieval) информации. Вместо того чтобы полагаться только на знания, зашитые в LLM во время ее обучения, RAG позволяет модели перед генерацией ответа сначала найти актуальную и релевантную информацию из заданного набора источников данных.

Если в LibreChat настроен RAG, то у вас появляется возможноть во время общения с LLM прикрепить текстовый файл (поддерживается множество форматов), чтобы сформировать запрос, связанный с информацией из текстового файла. Этот текстовый файл не будет напрямую передан LLM, а будет отправлен на индексацию во внешнюю (локальную или облачную) нейросеть (обычно не LLM), которая проиндексирует весь файл в семантические векторы (или эмбеддинги) и вернёт в LLM лишь часть информации, которая должна соответствовать вашему запросу, эту часть информации и будет обрабатывать LLM для формирования своего ответа.

Для локальной работы вы должны установить nomic-embed-text через ollama:

$ ollama pull nomic-embed-text

nomic-embed-text – это специализированная нейросеть, классифицируемая как модель текстовых эмбеддингов, которая преобразует текст в числовые векторы для задач семантического поиска и является ключевым компонентом в «Retrieval» (поиске) части RAG-систем.

Теперь открываете docker-compose.override.yml и дополняете его:

services:
  rag_api:
    image: ghcr.io/danny-avila/librechat-rag-api-dev:latest

Для работы с локальным RAG нужно установить контейнер с расширенным функционалом.

После вносите изменение в .env

RAG_API_URL=http://host.docker.internal:8000
EMBEDDINGS_PROVIDER=ollama
# Тут по идее настройки docker/podman будут разниться, вам может потребоваться настроить ollama так, чтобы она слушала все интерфейсы на 11434 порту
OLLAMA_BASE_URL=http://host.docker.internal:11434
EMBEDDINGS_MODEL=nomic-embed-text

Готово, теперь всё должно работать. Перезагружайте docker-compose, настройка завершена.


Я ещё не договорил

Очень важный момент. LLM не делает служебный запрос для поиска RAG в базовом сценарии (агенты могут работать иначе). Ваш начальный промпт обрабатывается не основной нейросетью, а той, что настроена для работы с RAG. Она не может переформулировать ваш запрос, соответственно, если пользователь задает очень общий, неоднозначный, многосоставный или просто плохо сформулированный вопрос, векторизация этого может привести к поиску нерелевантных фрагментов, которые и будут переданы основной LLM. RAG – это компромисс, который придуман для того, чтобы вы могли передать LLM текст, размер которого превышает контекстное окно LLM.

Самые передовые коммерческие LLM обладают очень большим контекстым окном, теперь они могут принять огромные объёмы текста, минуя этот компромисс (это будет стоить вам дороже). В актуальном LibreChat v0.7.7 пока нет возможности прикрепить файл, минуя RAG. Эта функция должна быть добавлена в v0.7.8/9

Если вас интересует на базовом уровне, как выглядит взаимодействие с RAG:

  • Фаза 1: Индексация документа (когда вы прикрепляете впервые файл)
  1. Загрузка файла (Пользователь -> LibreChat):
    • Вы выбираете и прикрепляете текстовый файл (например, PDF, TXT, DOCX) в интерфейсе LibreChat.
  2. Отправка файла в RAG (LibreChat -> rag_api):
    • LibreChat получает файл.
    • Понимая, что этот файл предназначен для RAG, LibreChat отправляет содержимое этого файла (или ссылку на него, если он временно сохранен) на эндпоинт вашего rag_api (который слушает на http://host.docker.internal:8000).
  3. Обработка и Чанкинг (rag_api):
    • Сервис rag_api получает текстовые данные из файла.
    • Он разбивает (chunking) весь текст документа на более мелкие, управляемые фрагменты. Размер и метод разбивки (по параграфам, предложениям, с перекрытием) настраиваются в rag_api. Это важно, так как эмбеддинги создаются для каждого чанка отдельно, а не для всего документа целиком.
  4. Запрос на создание Эмбеддингов (rag_api -> Ollama):
    • Для каждого текстового чанка rag_api формирует запрос к Ollama.
    • Основываясь на ваших .env, rag_api отправляет api-запрос на http://host.docker.internal:11434 с текстом чанка, указывая, что нужно использовать модель nomic-embed-text для генерации эмбеддинга.
  5. Генерация Эмбеддингов:
    • Ollama получает запрос от rag_api.
    • Запускает модель nomic-embed-text.
    • Модель обрабатывает текст чанка и генерирует числовой вектор, представляющий семантическое значение этого чанка.
    • Ollama возвращает этот вектор обратно сервису rag_api.
    • Этот процесс повторяется для всех чанков документа.
  6. Сохранение в Векторную Базу (rag_api -> vectordb):
    • rag_api получает вектор от Ollama для каждого чанка.
    • rag_api подключается к вашей vectordb.
    • Для каждого чанка rag_api сохраняет в vectordb:
      • Сгенерированный вектор.
      • Оригинальный текст этого чанка.
      • Метаданные.
  7. Завершение Индексации: Процесс завершен. Векторные представления вашего документа теперь хранятся в vectordb и готовы к поиску. LibreChat может показать вам, что файл обработан.
  • Фаза 2: Запрос к базе данных
  1. Формулировка Запроса (Пользователь -> LibreChat):
    • Вы задаете вопрос в чате LibreChat, указывая, что ответ нужно искать с использованием прикрепленных ранее файлов.
  2. Передача Запроса в RAG api (LibreChat -> rag_api):
    • LibreChat отправляет ваш текстовый запрос в rag_api.
  3. Создание Эмбеддинга для Запроса (rag_api -> Ollama):
    • rag_api берет ваш вопрос и отправляет его в Ollama для создания эмбеддинга с использованием той же самой модели nomic-embed-text. Крайне важно использовать одну и ту же модель для индексации и для запросов.
  4. Генерация Эмбеддинга Запроса:
    • Ollama генерирует векторное представление вашего вопроса и возвращает его в rag_api.
  5. Поиск Похожих Веторов (rag_api -> vectordb):
    • rag_api использует полученный вектор запроса для поиска в vectordb.
    • Оно выполняет поиск семантической близости, чтобы найти N векторов, которые наиболее близки по смыслу к вектору вашего вопроса.
  6. Извлечение Контекста (rag_api -> vectordb -> rag_api):
    • rag_api получает из vectordb тексты тех чанков, чьи векторы оказались наиболее релевантными запросу. Это и есть извлеченный контекст.
  7. Возврат Контекста в LibreChat (rag_api -> LibreChat):
    • rag_api отправляет найденные релевантные текстовые чанки обратно в основной сервис LibreChat.
  8. Формирование Промпта для LLM:
    • LibreChat получает контекст от rag_api.
    • Он конструирует финальный промпт для LLM. Этот промпт обычно включает:
      • Системные инструкции.
      • Извлеченный контекст (текст релевантных чанков).
      • Ваш оригинальный вопрос.

Финал

Я познакомил вас с LibreChat, его базовой настройкой и функционалом. Специально для вас, я прошёлся только по самым верхам, поэтому мной совсем не затронута тема агентов и инструментов к ним, установка и взаимодействие со встроенным интерпретатором кода и многое другое. Особая благодарность админу-корректору, которому пришлось вычитать этот текст до конца. Возможно ты мой единственный зритель.

Источники:

 librechat, litellm, , ,

mamina_radost
()

Alacritty + Zellij: идеальное сочетание для терминала

Статьи — Desktop

Alacritty — быстрый и минималистичный терминальный эмулятор с поддержкой ускорения через GPU. Я попробовал много разных терминалов. Меня в принципе устраивал и Konsole, но он объективно подтормаживает и трудно кастомизируется. Из современных я пробовал Ghostty, Kitty, Wezterm, но у них всех недостатки. Они тянут слишком много зависимостей, а также запускают скрипты на Python или Lua. В Alacritty же нет ничего лишнего. И отдельно напишу, что он поддерживает более стабильный (как покосившийся сарай, который, тем не менее, падать не спешит) X.Org. Единственным его недостатком является отсутствие лигатур.

Установка

sudo pacman -S alacritty

Так же советую поставить темы для него:

sudo pacman -S alacritty-theme-git

Для этого должен быть добавлен репозиторий archlinuxcn. Также темы можно поставить из AUR.

Вручную:

git clone https://github.com/alacritty/alacritty-theme ~/.config/alacritty/themes

За быстроту приходиться платить отсутствием функционала. В этом эмуляторе терминала нет даже вкладок, поэтому потребуется мультиплексер — программа, которая может разделять экран, запуская в нём несколько сессий шелла. Обычно для этого используют tmux, но он всем давно приелся и не лишен недостатков, иначе бы не появился zellij. Ставим последний:

sudo pacman -S zellij

Если в дистрибутиве нет alacritty или zellij в репозиториях, то их можно поставить через стандартный пакетный менеджер Rust:

cargo install alacritty
cargo install zellij

Чтобы все работало, $PATH должен содержать путь до ~/.cargo/bin.

Настройка Alacritty

Создадим директорию для конфига:

mkdir -p ~/.config/alacritty

Пример конфига:

~/.config/alacritty/alacritty.toml

[general]
# тут указываем путь до темы
import = [
  "/usr/share/alacritty/themes/catppuccin_mocha.toml",
]

[window]
opacity = 0.96
startup_mode = "Windowed"
# у терминала можно отключить оформление, что может пригодиться любителям тайлинга
decorations = "Full"
dynamic_title = true

# Начальные размеры окна терминала при старте
[window.dimensions]
columns = 140
lines = 50

# Изменяем курсор
[cursor.style]
shape = "Beam"
blinking = "Always"

[scrolling]
# если у вас мало памяти, то уменьшите значение
history = 100000
multiplier = 3

[font]
size = 11.5

# Можно использовать свои шрифты для каждого варианта текста (normal, bold, italic)
[font.normal]
# Использовать лучше всего патченные Nerd-шрифты
family = "JetBrainsMono NF"

[terminal.shell]
# Запускаем при старте терминала zellij, при этом используя последнюю сессию
program = "/usr/bin/bash"
args = [
  "-l",
  "-c",
  "zellij attach --index 0 || zellij",
]

Настройка Zellij

Создадим конфиг:

mkdir -p ~/.config/zellij
zellij setup --dump-config > ~/.config/zellij/config.kdl

Редактируем его:

vim ~/.config/zellij/config.kdl

Что изменить:

ui {
    pane_frames {
        // Можно скрыть название сессии
        hide_session_name true
        // Или сделать края рамок круглыми
        //rounded_corners true
    }
}

// ...
keybinds {
    normal {
        // uncomment this and adjust key if using copy_on_select=false
        // bind "Alt c" { Copy; }
    }
    locked {        
        // Эти клавиши не будут доступны в том же Vim
        bind "Ctrl g" { SwitchToMode "Normal"; }

        // Я бы добавил сочетания для прокрутки экрана, так как встроенные, используемые Alacritty, не будут работать (zellij перехватывает это событие)
        // PageUp/PageDown не работают ни с какими сочетаниями, а поэтому на них
        // лучше ничего не вешать
        bind "Ctrl Shift Up" { PageScrollUp; }
        bind "Ctrl Shift Down" { PageScrollDown; }
        bind "Ctrl Shift Home" { ScrollToTop; }
        bind "Ctrl Shift End" { ScrollToBottom; }  
    }
    // Остальное все дефолтным оставляем
}

// Стартуем в заблокированном режиме
default_mode "locked"

// Можно сменить тему
// Список встроенные тем: https://zellij.dev/documentation/theme-list.html
theme "onedark"

// Если этот режим включен, то не работает вставка средней кнопкой мыши, а когда
// выключен — в Vim клик мыши не перемещает курсор
mouse_mode true

// Размер буфера прокрутки в строках. Приводит к увеличению используемой оперативной памяти
scroll_buffer_size 100000

// Kitty официально не поддерживает ни Tmux, ни Zellij. Совместимость с ним можно вырубить, если есть какие-то проблемы
support_kitty_keyboard_protocol true

// Если раздражают различные сообщения при запуске
show_release_notes false
show_startup_tips false

Как пользоваться Zellij?

При использовании настроек выше, вы стартуете в заблокированном режиме, когда ввод в терминал игнорируется. Чтобы перейти в нормальный режим (управления сессиями, интерфейсом), нажмите Ctrl-g. Чтобы разбить экран, нажимаем Ctrl-p, d. Между сплитами можно переключаться с помощью Alt и стрелок. Все доступные сочетания выведены на экран. Как только закончите, нажмите Ctrl-g чтобы начать работать с терминалом.

Zellij так же поддерживает сессии, между которыми можно переключаться… А так же, если вы просто закроете терминал (вместо exit или quit), то сессия сохранится, и процессы будут крутиться в фоновом режиме.

Пример команд:

zellij ls              # Список сессий
zellij attach -c work  # Создать/присоединиться к сессии
info zellij            # Справка

Настройка SSH

Так как по умолчанию переменная $TERM в alacritty содержит недопустимое значение с точки зрения удаленного сервера, то при запуске ssh могут не работать некоторые сочетания клавиш. Чтобы это исправить, добавьте в ~/.ssh/config:

Host *
  SetEnv TERM=xterm-256color

На сервере, как правило, нет terminfo для alacritty, поэтому сервер в ответ шлет escape-последовательности, которые alacritty понимает неправильно. Мы можем использовать способ выше либо скопировать terminfo на сервер:

# На хосте экспортируем файл terminfo
infocmp alacritty > alacritty.info

# Копируем его на удаленный сервер
scp alacritty.info user@server:/tmp/

# А затем устанавливаем на удаленном сервере
tic -x /tmp/alacritty.info

Полезные заметки

  • В Allacrity Vi-mode переключается сочетанием клавиш Ctrl + Alt + Space.
  • Через Ctrl + Shift + F можно искать текст на экране.
  • Все сочетания Alacritty перечислены здесь.
  • При использовании Zellij, сочетания Alacritty для прокрутки не работают. Как альтернативу можно использовать режим поиска: Ctrl-g, Ctrl-s, ↓/↑/j/k, Esc… или настройки выше

 ,

rtxtxtrx
()

Установка FreeDOS в DOSBox-X

Статьи — Desktop
Установка FreeDOS  в DOSBox-X

FreeDOS — свободная операционная система, совместимая с MS-DOS.

Приведу пару примеров использования. Компании Dell, HP и Lenovo производили и поставляли персональные компьютеры с предустановленной операционной системой FreeDOS, так как это снижает общую стоимость компьютера по сравнению с предустановленной ОС Windows. Также FreeDOS устанавливается на современные ноутбуки ASUS и Samsung.

Этот метод установки использует загрузку Freedos 1.4 Legacycd.

Создание образа HDD

Примечание: В дополнение к утилите командной строки, можно создать образ жесткого диска из меню DOSBox-X. Перейдите в меню «DOS» и выберите «Create blank disk image…». Эта опция позволяет создавать различные общие типы жестких дисков. Для менее распространенных типов вам необходимо использовать утилиту командной строки.

В командной строке Dosbox-X:

IMGMAKE hdd.img -t hd -size 2048
IMGMOUNT C hdd.img
Приведенный выше пример создает образ HDD объёмом 2 GiB. Очевидно, вы можете использовать другой размер до 2 TiB.

Установка Freedos

Следующим шагом является установка и загрузка образа CD ISO.

IMGMOUNT D FD14LGCY.iso
IMGMOUNT A -bootcd D
BOOT A:
Теперь он будет загружаться в программу настройки. Вы должны быть в состоянии следовать подсказкам установки на экране.

После того, как он закончил установку, он перезагрузит DOSBox-X, и вы снова увидите в подсказке Z: \>. Теперь введите следующее для загрузки FreeDOS:

IMGMOUNT 0 empty -fs none -t floppy
IMGMOUNT C hdd.img -ide 1m
IMGMOUNT D empty -t iso -ide 2m
BOOT C:
Эти последние строки могут быть добавлены в раздел [AutoExec] вашего файла конфигурации DOSBox-X для автоматического монтирования и загрузки FreeDOS.

P.S. Эта статья является фактически переводом статьи с официального сайта Dosxbox-X, немного адаптированная под версию FreeDOS 1.4.

 ,

vbcnthfkmnth123
()

X11 robustness: DRI3 без аппаратного ускорения

Статьи — Разработка
X11 robustness: DRI3 без аппаратного ускорения

Уже больше 5 лет как использую разные GPU от AMD и сталкиваюсь с разнообразными проблемами, приводящими к его зависанию. К сожалению, в современном линуксовом десктопе, особенно на AMD зависание GPU зачастую не получается обработать прозрачно для софта. В лучшем случае могут попортиться данные, связанные с активными задачами, в худшем - gpu вообще становится неработоспособен до снятия питания (этим грешили некоторые APU на gfx9)

Успешный GPU Reset

Если в случае с неработоспособным GPU другого выхода, кроме как отказ от него нет, дискретные GPU обычно могут успешно пройти сброс.

На моём опыте, с rx580 это приводило к превращению всего содержимого VRAM в шум, на котором слегка можно было различить контуры ранее находившихся там текстур.

На rdna2 же обычно содержимое vram остаётся целым, однако не всегда. С некоторой вероятностью обнаруживается периодический мусор после сброса, обычно в этом случае попытка использовать работающие с GPU приложениями приведёт к повторному зависанию. В общем, никаких гарантий нет и по хорошему все GPU клиенты должны пересоздать все контексты с нуля

Direct Rendering Interface - как это работает

Чтобы вывести текстуру на экран, кто-то должен её разместить в определённом прямоугольнике, с учётом возможного перекрытия другими окнами. Для этого есть разные способы. Где-то раньше применялись маски и color key, где-то просто оверлей - всё это было частью 2D ускорения.

Для indirect рендеринга (AIGLX) всё просто - там рисует opengl на экран сам X11 и может использовать встроенные возможности драйвера по указанию ViewPort. Для direct rendering же всё несколько сложнее.

В DRI2 иксы открывают устройство, создают текстуру для вывода на экран и отдают буфферы на клиент через GEM flink. Так же иксы авторизуют доступ к устройству. Сильно не углублялся в первую версию DRI, но вероятно там вместо gem handle драйвер оперировал физическими адресами напрямую. DRI3 сильно упрощает этот механизм. В эпоху DRI3 у процессов есть доступ к GPU независимо от x-сервера - они могут рендерить всегда. Иксы только реализуют вывод на экран. Причём приложение может как запросить буффер у иксов, так и отправить туда буффер уже отрендеренной текстуры.

Остаётся вопрос - как же эту текстуру поместить в окне?

Ситуация с 2D ускорением

В отличие от других ОС, в linux решили отказаться от 2D ускорения. Это достаточно печально, так как в отличие от opengl композитинга и glamor, 2d ускорение могло бы совсем не зависеть от состояния внутри GPU. Обычно реализации 2D ускорения представляют из себя некоторый stateless command buffer, который ни от каких внешних факторов не зависит. В общем, вероятно, будь у нас 2д ускорение - можно было бы просто перезапустить софт, который привёл к сбою и продолжать работать.

Наиболее активно используемой реализацией 2D ускорения является glamor, работающий поверх opengl. Это чем-то напоминает композитный менеджер, но не умеющий прозрачность и работающий прямо внутри иксов. Сейчас же все xf86-video драйвера, реализующие 2д ускорение, заброшены (intel не работает на последних gpu, nouveau работает даже хуже modesetting и не может в dri3), а разрабы рекомендуют использовать modesetting или вообще wayland. Для amdgpu 2d ускорения и не было никогда, а отключение glamor на amdgpu отключит и dri3. А в wayland такого ускорения вероятно никогда и не будет, т.к добавить его сразу во все реализации, да ещё и для всех GPU практически нереально. Да и ускорять в wayland нечего - приложения уже отдают/получают отрисованные поверхности

Отключение glamor - что произойдёт?

Возможно с этим сталкивались те, кто пробовал использовать xf86-video-intel на последних поколениях, совместимых с i915. Там отключается ускорение и с драйвером iris это может приводить к некорректной отрисовке.

Если включить dri3 «в лоб» - мы увидим opengl текстуры, но они не будут обновляться, так и оставшись на первом кадре. То же самое происходит, если принудительно включить dri3 в коде amdgpu. Причина же в том, что нет возможности прочитать текстуру из CPU напрямую, а при отключении ускорения иксы используют CPU путь.

msdri - патчи, которые так и не смержили

С проблемой столкнулись и на raspberry pi, только там причиной нестабильности драйвера был лимит CMA памяти в 256мб:

https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/945

Реализация GBM на Raspberry PI немного отличается. потому там включение dri3 помогло.

Вероятно, из-за того, что патч не работает на других GPU, его так и не смержили, хотя этот патченный xorg продолжает использоваться в Raspberry OS

TigerVNC - работоспособная, но медленная реализация

В комментариях к предыдущему патчу отметился разработчик TigerVNC, в котором проблема уже решена. Его реализация dri3 перед каждой операцией отрисовки копирует текстуру целиком на CPU буффер, вызывает программную отрисовку, после чего копирует обратно. Медленно, но как-то работает. В целом этого уже достаточно, чтобы пользоваться системой с иксами и не бояться GPU reset. Но не совсем удобно конечно, особенно если dri3 нужен для игор или их разработки.

Out-of-tree драйвер. Эксперименты с GBM

xf86-video-modesetting зачем-то был вмержен в основное дерево Xorg. Цели этого неясны, но работать с in-tree драйвером куда сложнее.

Потому первое что было предпринято - код из ветки с msdri3 был перенесён поверх старого xf86-video-modesetting на autotools. Почему не meson? ИМХО, meson вместо того, чтобы исправить проблемы autotools, перенял их, потому тратить время на то, чтобы с ним разобраться того не стоит. Обе системы сборки максимально плохо выполняют свою задачу и потягаться с ними наверно может только SConstruct.

Что получилось? После пары правок, modesetting собрался. Ещё пара правок и заработал даже glamor.

Но с AccelMethod=msdri3 пусть приложения и запускались, на экране застрявал первый выведенный кадр.

Дальше я начал пытаться вместо GBM пытаться замаппить текстуру напрямую. Если на intel это даже срабатывало (пусть и с неправильным тайлингом), на amdgpu mmap просто падал. Всё верно - текстура не была аллоцирована как CPU_VISIBLE. Да если бы и была, то читалась бы она со скоростью около 50 мегабит в секунду - доступ к некэшированной памяти очень медленный. Так же я попробовал amdgpu-шные ioctl для маппинга, но они тоже не помогли, да и хочется получить универсальное решение. Обновил исходники modesetting до последней версии (позже выяснил, что это было ошибкой) и решил начать делать с нуля.

Доступ к текстурам из CPU - как это работает

Все текстуры представляются на уровне ядра как dmabuf, который передаётся между процессами в виде файлового дескриптора.

Внутри процесса тексстура может быть представлена помимо дескриптора как prime handle и gem flink.

gem flink - тоже глобальный дескриптор текстуры, но привязанный к конкретному GPU - этот способ появился ещё до унификации текстур в dmabuf, в эпоху dri2, а prime handle валиден только для конкретного открытого дескриптора из /dev/dri - то есть это дескриптор уже открытого gem объекта. В dri2 текстуры передавались только от иксов в клиент через gem flink, в dri3 же в обе стороны.

Для унификации и работы с этими самыми gem’ами используется GBM - то есть каждая текстура для нас представляет пару dmabuf fd и непрозрачную структуру gbm_bo. В более новых версиях появились drm modifier, определяющие формат и способы работы с текстурой, но реализовывать их пока не обязательно. Так же GBM реализует аллокацию, импорт, освобождение текстур, но делается всё это не через стандартные DRM API, а через приватные реализации для конкретного GPU.

Конечно использовать GBM - не лучший способ, т.к он всё равно лезет в реализацию opengl (но не использует сам opengl, а оперирует с теми stateless API, что предоставляет drm драйвер).

К сожалению, никаких API для чтения/записи/flush текстур нет, однако если присмотреться к описанию флагов для gbm_bo_map:

/**
 * Flags to indicate the type of mapping for the buffer - these are
 * passed into gbm_bo_map(). The caller must set the union of all the
 * flags that are appropriate.
 *
 * These flags are independent of the GBM_BO_USE_* creation flags. However,
 * mapping the buffer may require copying to/from a staging buffer.
 *
 * See also: pipe_map_flags
 */
enum gbm_bo_transfer_flags {
   /**
    * Buffer contents read back (or accessed directly) at transfer
    * create time.
    */
   GBM_BO_TRANSFER_READ       = (1 << 0),
   /**
    * Buffer contents will be written back at unmap time
    * (or modified as a result of being accessed directly).
    */
   GBM_BO_TRANSFER_WRITE      = (1 << 1),
   /**
    * Read/modify/write
    */
   GBM_BO_TRANSFER_READ_WRITE = (GBM_BO_TRANSFER_READ | GBM_BO_TRANSFER_WRITE),
};

void *
gbm_bo_map(struct gbm_bo *bo,
           uint32_t x, uint32_t y, uint32_t width, uint32_t height,
           uint32_t flags, uint32_t *stride, void **map_data);

Становится понятно, почему трюк из msdri3 не сработал: GBM_BO_TRANSFER_READ_WRITE гарантирует только последовательность read-modify-write.

То есть нам не нужно копировать всю текстуру вручную, достаточно замаппить её через GBM_BO_TRANSFER_READ_WRITE на время операции записи, а так же повторно маппить с GBM_BO_TRANSFER_READ на время чтения.

Первая реализация - заставляем GL/Vulkan рисоваться

Для простоты вместо актуальной версии dri3 я указал самую первую - она требует предоставить всего 3 функции - открытие девноды, импорт внешнего дескриптора как Pixmap и получение дескриптора для существуюшего Pixmap. Для отрисовки opengl приложений обычно хватает первых двух.

Открытие ноды взял как есть из msdri3:

https://gitlab.freedesktop.org/cpmichael/modesetting/-/blob/ms_dri3/hw/xfree86/drivers/modesetting/dri3.c#L278

Остальные изменения из msdri3 пока не пригодились, sync заработал просто включением стандартного SyncShm и этого достаточно для работы приложений

Заставить текстуры обновляться оказалось проще простого - перед чтением текстуры исполняется screen->SourceValidate - в нём можно и подготовить текстуру к чтению. И вот тут то я столкнулся с проблемой. Если я просто заменяю SourceValidate на свой - всё работает как надо и текстура обновляется, но стоит мне вызвать оригинальную функцию - мой враппер перестаёт работать… Но в любом случае, без вызова оригинальной функции текстура уже начала обновляться

Страшные костыли в xfree86 и почему (возможно) разрабы ушли на wayland

Был убит целый день, на то, чтобы разрбраться почему врапперы не работают. Так же я пытался враппить функции отрисовки, но безуспешно.

В конце концов я выяснил, что SourceValidate переписывает адрес на какой-то другой. Гугление привело вот к такому врапперу:

https://cgit.freedesktop.org/xorg/xserver/commit/?id=b89e6dbdfbb50e3b5bc7fcb7eccc397c467c92f8

Чуть позже я выяснил, что остальные врапперы не работают по этой же причине - только вот переписывание их повторно вообще отправляло иксы в вечную рекурсию.

Да, вызов каждой функции отрисовки может переписывать функции в Screen по нескольку раз. Это создаёт хитрые и довольно сложные для понимания чейны вызовов - ты никога не знаешь, какой модуль будет дальше и что он будет делать - одни модули переписывают функции, другие просто вызывают не переписывая, из-за чего и получалась рекурсия…

Вторая проблема, которая тоже отняла много времени - это сломанный код в апстриме.

Ещё несколько лет назад, чтобы исправить тиринг (которого нет на специализированных дровах вроде amdgpu, но есть на modesetting) в иксы начали внедрять TearFree, попутно ломая внутренние API.

И пусть сам TearFree отключается (мне пришлось его выключить из-за более старой версии xorg в системе), изменения затрагивают и обычный код обновления/vblank…

И судя по всему, даже несмотря на наличие таких прекрасных инструментов, как address sanitizer, эти новые изменения никто не проверял. Когда мне надоело ловить странные зависания и переписывания указателей и я начал запускать xorg с предзагруженным санитайзером, нашлось несколько use-after-free. И пусть мне кое-как удалось их заткнуть (не уверен в корректности логики), я в итоге вернулся на старую ветку, с которой экспериментировал ранее. Да, xf86-video-modesetting в апстриме не готов, не используйте его по возможности.

Тиринг, композитинг, отрисовка

Когда олучилость заставить dri3 поверхности обновляться, столкнулся с другой проблемой - захват окон через xcomposite не работает. Да и вообще, gl окна рисуются с постоянным тирингом, который на софтовой отрисовки вряд ли получится исправить.

С полноэкранными окнами дела обстоят несколько проще - для них предусмотрен механизм PageFlip. Когда окно покрывает всю поврехность экрана. драйвер отправляет его текстуру напрямую в kmsdrm, тем самым минуя программную отрисовку. Это уже было реализовано в msdri3, потому сделал по аналогии - glxgears -fullscreen стал рисоваться без тиринга.

Таким образом, я могу использовать иксовые opengl композиторы и тогда оконные приложения тоже смогут избежать тиринг - надо только починить функцию редиректа окон в composite

В dri3 это отчевает третья функция - pixmap_to_fd - именно она вызывается, когда клиент делает XCompositeNameWindowPixmap чтобы представить текстуру чужого окна в opengl.

Причём мало того, что она должна экспортировать существующую пиксмапу как текстуру, так ещё и держать её в актуальном состоянии.

Тут то мне и потребовалось перегружать всю отрисовку. Чтобы понять, какие функции могут писать пиксмапы, пришлось подсмотреть в код glamor и его врапперы для amdgpu. А так же куча времени потрачена на попытки не переписывать врапперы, но в итоге просто была написана общая функция, возвращающая указатель на момент вызова, на подобии того враппера для SourceValidate:

// call function, but restore original pointer (some methods may replace API)
#define CallWrap(real, saved, func) do { void *tmp = real; real = saved, func, saved = real, real = tmp;} while(0)

Композитинг заработал, но производительность оставляет желать лучшего - в окне терминала konsole (версия из TDE) отрисовка каждого символа дёргает маппинг на запись, а потом обратно на чтение, тем самым вызывая аж 3 копирования тексуры целиком. Это можно попытаться оптимизировтаь, проверяя состояние текстуры и пропуская синхронизации, но здесь возможен и другой путь.

DUMB Buffer и прямой маппинг памяти

В отличие от полученных от приложений текстур, текстуру для иксовых пиксмап выделяет сам иксовый драйвер. И она может быть CPU_VISIBLE и маппиться напрямую, а не через staging buffer. GBM так сделать не позволяет, однако он позволяет импортировать текстуру созданную другим способом. И этот способ есть в KMSDRM устройствоах - DRM_IOCTL_MODE_CREATE_DUMB. Это универсальное API для аллокации текстуры с DMABUF, который можно читать и писать из CPU, но к сожалению только в синхронном режиме. Текстура, выводимая на экран в modesetting без glamor создаётся именно через него.

Первая мысль была - замапить dumb object напрямую для всех текстур, для которых получается pixmap’а… Но не тут то было - когда я это сделал, после запуска композитора иксы зависли в memcpy навечно…

Да, чтение без кэша настолько медленное, что пришлось подключаться по ssh и прибивать иксы удалённо - пускай там и 50 мегабит в секунду, вывод нескольких кадров без композитинга копировал много текстур и застопорился надолго…

К сожалению, DRM api не имеет универсального способа управление кэшем. Есть приватные команды для конкретных GPU, есть кастомные dmabuf драйверы, но через сам drm включить кэширование нельзя.

Потому как обходной способ я стал включать dmabuf только на время записи, для чтения возвращая старый CPU указатель, который иксы обновят при необходимости через SourceValidate.

Производительность, конечно, всё равно неидеальная, konsole всё ещё подтормаживает, но не так дико, как с лишними копирвоаними текстуры.

Ну и конечно не хватает какого-либо ускорения. В идеале хотя бы замаппить текстуры через Vulkan, при необходимости откатываясь на программную реализацию. Даже для маппинга текстур он предоставляет куда большие возможности, чем GBM.

Исходный код получившегося драйвера доступен по ссылке:

https://git.disroot.org/mittorn/xf86-video-modesetting/src/branch/softdri3-old Это вынесенный обратно в out-of-tree modesetting драйвер, поскольку с ним так будет проще работать. Для установки не обязательно ставить в систему, можно установить прямо в хомяк:

./configure --prefix /home/user/xf86-video-modesetting-prefix --with-xorg-module-dir=/home/user/xorg-modules
make install

И прописать в xorg-conf

Section "Files"
        ModulePath "/home/user/xorg-modules"
        ModulePath "/usr/lib64/xorg/modules"
EndSection

Другие изменения

Так как изначальная задача касалась работы в условиях возможного GPU reset, с ним есть и другие нюансы.

К сожалению, GPU reset не всегда происходит успешно. Возможно, из-за багов в прошивке или драйвере SMU, а возможно из-за особенностей конкретного GPU, часто через 3-4 секунды после gpu reset, резетилась железно вся система, если софт продолжал что-то рендерить.

Т.к я использую довольно старое ядро и не хочу пока обновлять, немного попатчил его, чтобы обойти проблему:

diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
index 49f734137f15..ba67bdd65b47 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
@@ -4794,7 +4795,7 @@ int amdgpu_do_asic_reset(struct list_head *device_list_handle,
 				if (r)
 					goto out;
 
-				vram_lost = amdgpu_device_check_vram_lost(tmp_adev);
+				vram_lost = false; //amdgpu_device_check_vram_lost(tmp_adev);
 				if (vram_lost) {
 					DRM_INFO("VRAM is lost due to GPU reset!\n");
 					amdgpu_inc_vram_lost(tmp_adev);
@@ -5060,7 +5061,9 @@ int amdgpu_device_gpu_recover_imp(struct amdgpu_device *adev,
 	/*
 	 * Special case: RAS triggered and full reset isn't supported
 	 */
-	need_emergency_restart = amdgpu_ras_need_emergency_restart(adev);
+	need_emergency_restart = 0; //amdgpu_ras_need_emergency_restart(adev);
+	if(amdgpu_ras_need_emergency_restart(adev))
+		DRM_WARN("EMERGENCY REBOOT REQUESTED!!!");
 
 	/*
 	 * Flush RAM to disk so that after reboot
@@ -5072,9 +5075,17 @@ int amdgpu_device_gpu_recover_imp(struct amdgpu_device *adev,
 		ksys_sync_helper();
 		emergency_restart();
 	}
+	
 
 	dev_info(adev->dev, "GPU %s begin!\n",
 		need_emergency_restart ? "jobs stop":"reset");
+	{
+	char path[] = "/gpu_reset_helper.sh";
+	char *argv[] = {path, NULL};
+	char *envp[] = {NULL};
+	int ret = call_usermodehelper(path, argv, envp, UMH_WAIT_PROC);
+	printk("helper script ret=%d\n", ret);
+	}
 
 	if (!amdgpu_sriov_vf(adev))
 		hive = amdgpu_get_xgmi_hive(adev);

Хотел сначала на pastebin залить, но тот устроил мне DoS атаку через cloudflare и часть статьи была утеряна :(

  • Во-первых, выставляю vram lost всегда в 0, чтобы софт мог продолжать работаь
  • Во-вторых, вызываю usermode helper перед началом резета.

Хелпером является специальный скрипт, который останавливает все процессы и переключает VT. Делался он ещё до софтовой реализации dri3, но с ней работает надёжнее.

Пример скриптов, заточенных под используемый софт

/gpu_reset-helper_sh

#!/bin/sh
echo reset >> /tmp/reset_helper_log
/sbin/start-stop-daemon -S -b -x /emergency-chvt.sh
for p in $(lsof -t /dev/dri/card0 /dev/dri/renderD128); do
case $(readlink /proc/$p/exe) in
/usr/bin/Xorg)
echo skipping Xorg >> /tmp/reset_helper_log
;;
*hlvr*|*.exe|*chamfer*|*monado-service|*wivrn-service|*ovr-utils*)
echo killing $p $(/usr/bin/readlink /proc/$p/exe) >> /tmp/reset_helper_log
kill -9 $p
;;
*)
echo stopping $p $(/usr/bin/readlink /proc/$p/exe) >> /tmp/reset_helper_log
kill -STOP $p
;;
esac
done
echo done >> /tmp/reset_helper_log
sleep 0.5

emergency-chvt.sh

#!/bin/sh
/usr/bin/chvt 1
sleep 0.6
export DISPLAY=:0
for p in $(lsof -t /dev/dri/card0 /dev/dri/renderD128); do
echo $p
if [ $(cat /proc/$p/status|grep State:|cut  -f2 |cut -d ' ' -f1) = "T" ]
then echo T;for wid in $(/usr/bin/xdotool search --pid $p --onlyvisible); do /usr/bin/xdotool windowminimize $wid; done
fi
done

После gpu reset необязательные процессы вроде игор прибиваются, а все остальные останавливаются и по возможности сворачиваются.

Свёрнутые остановленные окна twin (из TDE) автоматически предалгает развернуть с SIGCONT.

После последних изменений в убиваемые приложения скорее всего добавится ещё и композитор

 , , ,

mittorn
()
: Быстрый HTTPS для сайта с помощью angie (форк nginx) (17 комментариев)

Пользовательские сервисы OpenRC: инструкция по применению

Статьи — Desktop

Как я уже писал раньше, в систему инициализации OpenRC недавно добавлена возможность запускать сервисы в пользовательской сессии. В этой статье я покажу, как этим пользоваться, на примере pipewire в Alpine Linux.

Что было раньше

Раньше в пакете с pipewire поставлялся (и до сих пор поставляется) скрипт /usr/libexec/pipewire-launcher, который предлагалось прописывать в конфиге sway. Особенность этого сетапа в том, что после остановки Sway все запущенные им в background процессы оставались висеть в памяти, и перед последующим запуском их предлагалось прибивать с помощью pkill. Не говоря уже про полное отсутствие логов, их не было.

Чтобы решить эти проблемы, нужно запускать pipewire в пользовательской сессии под супервизором. Собственно я так и делал при помощи s6, однако добавление пользовательских сервисов в OpenRC, а также соответствующих конфигов в пакеты в репозиториях Alpine позволяет отказаться от этих скриптов и пользоваться тем, что поддерживают мейнтейнеры дистрибутива.

Версии

Пользовательские сервисы были добавлены в OpenRC 0.60. Версия в репозиториях Alpine Edge на данный момент - 0.60.1. Используется pipewire 1.4.1 и wireplumber 0.5.8.

Зависимости

Для поддержки пользовательских сервисов нужно установить пакет openrc-user. Он содержит необходимые исполняемые файлы (openrc-user и openrc-user-pam), а также PAM-модуль pam_openrc.so.

PAM

Есть два способа запуска пользовательской сессии OpenRC: как сервис

$ doas rc-service user.${USER} start

и через PAM.

Предпочтительным является второй способ. Для того чтобы им воспользоваться, нужно подключить соответствующий PAM-модуль стандартным способом:

$ cat /etc/pam.d/base-session 
# ...
-session optional pam_rundir.so
session optional pam_openrc.so

Если $XDG_RUNTIME_DIR не создается автоматически, то нужно об этом позаботиться. Для этого я применяю еще один модуль pam-rundir (есть в репозиториях).

Перезагружаемся и проверяем, что пользовательская сессия работает:

$ rc-status --user

После этого можно запускать сервисы.

Запуск сервиса

В репозитории Alpine Linux уже начали добавлять файлы конфигурации пользовательских сервисов для разных пакетов, поэтому руками ничего писать не надо (если вы конечно не хотите запилить что-то свое кастомное):

$ rc-update --user add dbus default

Эта команда заставляет dbus автоматически запускаться при старте пользовательской сессии, являясь аналогом systemctl --user enable dbus. Симлинки на включенные сервисы располагаются в папке ~/.config/rc/runlevels, а сами конфиги лежат в /etc/user/init.d и /etc/user/conf.d.

Переменные окружения

DBus относится к типу сервисов, которые должны устанавливать особую переменную окружения, в данном случае $DBUS_SESSION_BUS_ADDRESS, для всех кто от них зависит. К таким зависящим относятся пользовательские сервисы и Sway. Раньше был только Sway, и подобная зависимость легко решалась тем что он запускался как потомок dbus-run-session:

cat /usr/share/wayland-sessions/sway.desktop | grep dbus
Exec=dbus-run-session /usr/bin/sway

Однако сейчас такой фокус не пройдет, потому что пользовательские сервисы запускаются независимо от Sway. И больше того, в OpenRC пока нет механизма, позволяющего сервисам подтягивать переменные окружения из их зависимостей. PR создан, но пока не смержен, поэтому $DBUS_SESSION_BUS_ADDRESS надо устанавливать вручную. Для этого предлагается использовать следующий кусок кода:

$ source /etc/user/conf.d/dbus

$ # в файле содержится вот такое:
$ cat /etc/user/conf.d/dbus
export DBUS_SESSION_BUS_ADDRESS="unix:path=$XDG_RUNTIME_DIR/bus"

Прописываем его перед запуском Sway, убрав там dbus-run-session, а также в conf.d всех релевантных пользовательских сервисов.

pipewire

Наконец, после всех этих манипуляций, можно запускать pipewire:

$ rc-update --user add pipewire default
$ rc-update --user add wireplumber default
$ rc-update --user add pipewire-pulse default

…работает! Для себя я сделал вывод, что несмотря на то что реализованы еще не все желаемые фичи, такой сетап уже вполне пригоден к использованию. На данный момент пользовательские сервисы добавлены для следующих пакетов в репозиториях Alpine:

  • gnome-keyring
  • kanshi
  • pipewire
  • wireplumber
  • wlsunset
  • dbus

 , , ,

Lrrr
()

Гитара + Linux + СПО: быстрое руководство 2025

Статьи — Desktop

Рассмотрим сначала базовую задачу — как играть, при использовании преимущественно СПО. Про запись гитары, и про коммерческое ПО - возможно, будет отдельно.

Итак, у вас есть гитара, и вы хотите использовать компьютер как гитарный процессор для нее, вместо гитарного комбика. Что надо знать, актуальный список пунктов на 2025 год:

Подключение гитары

Встроенная карта компьютера имеет только вход под электретный микрофон (самый дешевый «для скайпа»), и, если повезет, линейный вход. Ни то, ни другое не подходит для подключения гитары! Вы можете подключить гитару в эти входы, и что-то даже будет звучать, но звук будет сильно ухудшен по сравнению с нормальным подключением к гитарному (инструментальному) входу. Можно немного улучшить ситуацию, спаяв предусилитель, согласующий гитару и микрофонный вход. Гуглите: «истоковый повторитель для гитары», «DI-box для гитары».

Для нормального подключения гитары, нужен аудио-интерфейс (дискретная звуковая карта) с инструментальным входом. С ним гитара будет работать как надо из коробки. Настоятельно рекомендуется этот вариант. На 2025 год, самые базовые и дешевые, но безусловно качественные варианты: Focusrite Scarlett Solo 4gen, Arturia MiniFuse1. Обе эти карты из коробки работают в Linux.

Обеспечение низких задержек при обработке звука

Если вы хотите играть через компьютер без внешнего мониторинга, слушая звук, обработанный самим коспьютером, то нужно добиться минимальной задержки между входным и выходным сигналом звуковой карты.

Для игры на гитаре, при игре в наушниках через компьютер, желательно добиться задержки в пределах 4-6 мс. Это соответствует задержке при игре через традиционный аналоговый комбик, который стоит в 1.5 метрах от вас. Более высокая величина задержки будет мешать попадать точно в ритм и ровно играть быстрые пассажи. Для начинающего гитариста, высокая задержка может стать сильным барьером к росту техники игры. К этому надо отнестись очень серьезно!

Для Linux систем, задержка <5 мс является серьезным вызовом. Заставить систему работать с такой низкой задержкой не так просто, тут надо применить некоторые танцы с бубном! НО — возможно что у вас реально мощная производительная система, и задержка уровня 5 мс будет достигнута легко, в таком случае дальнейшие пункты вам могут показаться преувеличением. Сразу говорю, что пишу со слабой системы для слабых систем!

Важнейнше! Реальная, физическая величина задержки (roundtrip) может быть совсем иной, чем это показывает софт! Например, у меня в Ardour показывается величина задержки 1.3 мс, при этом реальная задержка может составлять, при разных настройках Pipewire, от 3.8 мс до 15 мс! Поэтому, крайне желательно измерять реальную задержку путем соединения входа и выхода звуковой карты кабелем, используя плагин LSP Latency Meter. Надо промерять задержку для каждой комбинации настроек, которые вы делаете, и ориентироваться на результаты измерений, а не на показания программ типа Ardour или QJackCtl.

Многочисленные руководства по снижению задержки, которые можно найти в интернете, зачастую устарели на 20 лет, поэтому лучше к ним относиться скептически.

  1. Установка linux-rt ядра: На 2025 год, его использование часто не дает улучшения задержки аудио. В то же время, обычное общего назначения ядро Linux, давно улучшено для аудио задач, и может давать очень низкие задержки аудио! Можно попробовать установить ядро linux-rt для эксперимента и сравнения с обычным ядром, но не стоит ждать от него заметного результата.

  2. Не все дистрибутивные ядра одинаково хороши! Например, штатное ядро Arch Linux у меня не может работать с приемлемым уровнем задержки. А ванильное ядро с kernel.org, без каких-либо дистрибутивных патчей - работает без нареканий. Поэтому требуется подбор ядра, для вашей системы! Я рекомендую пробовать сразу ванильное LTS ядро Linux, без дистрибутивных патчей. В Arch Linux в репозитории есть LTS ядро, оно практически ванильное и нам подходит. Также, хорошо себя показывают себя ядра Xanmod, Liquorix.

  3. Широко распространено мнение, что для низких задержек обязательно надо ставить и использовать JACK или Pipewire. Это не всегда так. Pipewire и JACK это отдельные процессы от приложения, которое обрабатывает гитарный звук. Поэтому требуются дополнительные затраты на переключение контекста, если вы выводите звук через JACK либо Pipewire. Это, само по себе, явно не положительным образом сказывается на минимально достижимой задержке! Поэтому, для достижения максимально низкой задержки, надо не добавить, а исключить все дополнительные прослойки между приложением и звуковой картой! Приложение должно работать поверх чистой ALSA, но тут есть несколько нюансов.

  4. Есть Ardour и Reaper, и в них очень хорошо реализована работа поверх ALSA и JACK/Pipewire с экстремально низкими задержками. Лучше сразу пробовать работать в этих программах, и использовать этот результат как основу для сравнения с другими вариантами. С другими приложениями, минимально достижимая задержка может быть выше. Надо пробовать и сравнивать.

  5. Правильные настройки приложения-хоста для достижения минимальной задержки. Важно! Этот пункт сначала лучше пропустить, и проверить что будет без этих настроек. Если стабильная работа с частотой дискретизации 48000 Гц и буфером 64 семпла не достигается, можно пробовать сделать следующие пункты.

  6. Если используется Pipewire, то прежде всего надо переключить профиль звуковой карты в Pro Audio. Сделать это можно при помощи Pavucontrol. Открываем вкладку Configuration, там сразу видны настройки выбора профиля для всех доступных карт в системе. Выбираем профиль Pro Audio. Этот профиль отключает все программные обвязки над физической картой (split configuration и подобное), и самое главное - он переключает Pipewire в режим работы, аналогичный JACK и ALSA приложениям. Именно такой режим лучше подходит для работы с экстремально низкими задержками!

  7. Надо правильно настроить Ardour. В его настройках, есть раздел Производительность, там надо включить: 1) использование только одного ядра процессора, либо другой вариант из этого списка, проверяется экспериментально, что для вас лучше будет работать 2) Управление питанием: Lowest (prevent CPU sleep states) 3) Что делать с обработчиками: Использовать FlushToZero и DenormalsAreZero.

  8. Настройки Reaper. Открываем диалог Reaper Preferences. Находим в в дереве настроек Audio / Buffering. Настройка Allow live FX Multiprocessing On: 4 CPUs. Отключаем или уменьшаем количество CPU.

  9. При создании проекта в Ardour/Reaper, выбираем бэкенд ALSA, количество периодов — лучше 3, далее подбираем размер буфера, минимальный при котором будет без глюков воспроизводиться звук. Для игры на гитаре, нужно добиться хотя бы вариантов 96000 Гц / 128 семплов, 48000 Гц / 64 семпла. Такие настройки дадут реальную задержку порядка 5 мс.

  10. При работе Ardour или Reaper на ALSA, он полностью захватит звуковую карту и другие приложения ничего воспроизводить не смогут! С этим придется мириться и сложно бороться, потому что только исключив все побочное, можно добиться минимальной, экстремально низкой задержки. У проблемы есть два основных решения. 1) если у внешней аудиокарты есть несколько входов и аппаратный микшер, можно подать кабелем туда звук со встроенной аудио карты, работающей с высокой задержкой. 2) Можно использовать Pipewire (также JACK, но в 2025 году уже смысла для Ardour в нем нет). Вот именно для этого и начинает быть нужен Pipewire — если вам надо воспроизводить звук из другого приложения кроме Ardour, и при этом задержка должна оставаться минимально возможной!

  11. Если Ardour работает поверх Pipewire, он будет использовать все звуковые карты которые у вас есть! То есть, как внешнюю так и встроенную! А встроенная, мягко скажем, с низкими задержками может совсем плохо работать, и все порушит. Поэтому, может иметь смысл отключение встроенной аудио карты. Отключить ее можно через конфигурацию wireplumber, как это сделать в настоящий момент времени — лучше искать в Arch Wiki по Wireplumber, там есть пункт где написано как отключить карту. Отключаем встроенную карту, оставляем только внешнюю.

  12. Если Ardour запускать поверх Pipewire, он просто подхватит все дефолтные настройки сервера Pipewire (частота дискретизации, буфер). Зачастую, это не то что нужно, поэтому до запуска Ardour надо изменить параметры Pipewire командами (ставим частоту дискретизации 96000 Гц и буфер 128 семплов).

metadata -n settings 0 clock.force-rate 96000
metadata -n settings 0 clock.force-quantum 128

Враги низкой задержки — найти и уничтожить!

Еще раз повторюсь, что если у вас достаточно производительная система, это может не понадобиться, все будет работать с низкой задержкой и так. Если стабильная работа хотя бы на частоте дискретизации 48000 Гц с буфером 64 семпла не достигается, то:

  1. Любые тяжелые приложения, прежде всего браузеры — сразу закрываем. Должно работать только системное окружение, Ardour, и плеер из которого воспроизводится минусовка. Конечно, очень хочется минусовку запускать прямо с Youtube, но - это сильный удар по задержке.

  2. Сеть и Wifi! Особенно вайфай. Сильно вредит. Отключаем его путем выгруза модуля ядра драйвера Wifi карты:

sudo modprobe -r ath9k
  1. Энергосбережение. Отключаем все энергосбережение, до чего можно и железо дает дотянуться. Прежде всего вот так:
sudo cpupower frequency-set -g performance

После принятия этих мер, задержку должно получаться выставить на уровне о котором написано выше.

Программная часть

Прежде всего, это руководство ориентировано на свободные программные решения. Они и будут описаны дальше. Но, как мы все понимаем, это одна из узкопрофессиональных не-программистских сфер, и здесь свободные решения зачастую сильно проигрывают коммерческим решениям, с точки зрения пользователя. Если для вас недостаточно перечисленных дальше свободных проектов, используйте проприетарные DAW и плагины, которых под Linux сейчас доступно большое количество. Их обзор выходит за рамки этого руководства (я сторонник использования СПО).

В основном, для формирования звука нам понадобятся плагины. Есть несколько универсальных форматов плагинов, все эти плагины подходят к любому приложению-хосту, который поддерживает соответствующий стандарт. Основные форматы плагинов - это LADSPA, LV2, VST2, VST3. Часто, одни и те же плагины доступны сразу в нескольких форматах. Для использования плагинов, понадобится сначала выбрать приложение-хост, в которое плагины будут добавляться. Здесь есть два типа приложений-хостов:

  1. Педалборд, патчборд, модульный хост, модульный синтезатор, или модульная DAW. Может называться по-разному, для гитариста проще всего объяснить, что это эмулятор педалборда. То есть такое пространство, куда можно добавлять плагины-педали и соединять их кабелями любым нужным образом, а потом управлять по MIDI работой этих плагинов.

    1. Из свободных решений, ближе всего к такому - MOD Desktop. Отличный графический интерфейс, правда, это веб-интерфейс)) 200+ плагинов внутри. Есть гитарные нейро-усилители AIDA-X и NAM среди них. Также в принципе все эффекты, которые нужны для гитары.

    2. Можно использовать Carla, туда можно загружать плагины во всех основных форматах, но у нее нет удобного GUI для управления соединениями между плагинами, и есть проблемы с низким уровнем задержки.

    3. Можно использовать Bespoke, есть развитый GUI и удобный патчинг, правда у него направленность больше на модульный синтез.

  2. DAW (Digital Audio Workstation) общего назначения. С первого взгляда кажется, что это хуже и не так удобно, как использовать специализированные для живой игры программы-педалборды. Но у DAW есть серьезное преимущество - они имеют хорошие аудио-движки, оптимизированные для работы с экстремально низкими задержками. Они также поддерживают все основные форматы плагинов, и они поддерживают управление всем по MIDI. Основное отличие при работе с DAW - надо организовывать плагины не в виде свободного графа соединений, как в педалборде, а в виде последовательных цепочек на разных дорожках DAW. Также к преимуществам можно отнести, что в DAW вы сразу же можете и записывать гитару, а не только играть. В DAW легко воспроизводить минусовку, добавив ее в виде дорожки. Какие DAW рассмотреть для начала:

    1. Ardour - это лидер среди свободных DAW. Вызывает некоторые нарекания по сравнению с крутыми коммерческими DAW, прежде всего в задачах редактирования MIDI партий, но в качестве хоста плагинов для живой игры на гитаре, базовой записи и сведения результата - вполне годится (если вы не Abbey Road). Все остальные СПО DAW решения, пожалуй, будут еще хуже для этой задачи.

    2. Qtractor - вторая по «масштабу» свободная DAW. Точно так же может использоваться, но она имеет меньше возможностей и пользовательской базы, чем Ardour. Попробовать ее однозначно стоит тоже.

    3. Reaper - это коммерческая DAW, но мы ее тоже здесь упомянем, очень уж она хороша! Это очень легкая по размеру и потреблению ресурсов, но функционально развитая и удобная DAW. Как и Ardour, имеет развитые настройки движка для обеспечения минимальной задержки, работает как с ALSA, так и с JACK/Pipewire. Reaper лучше чем Ardour, но это не СПО и он стоит денег.

    4. Другие коммерческие DAW. Они есть, но их обзор уже за рамками этого руководства.

  3. Теперь рассмотрим наборы плагинов, которые можно использовать с перечисленными выше хостами. Снова делаем упор на СПО решения.

    1. Набор плагинов KPP (https://github.com/olegkapitonov/Kapitonov-Plugins-Pack). Есть в репах основных дистрибутивов. Это непосредственно базовые гитарные плагины, поддерживают профили гитарного усилителя, позволяющие одним плагином имитировать практически любую модель комбика с любым типом звучания. К ним есть приложение tubeAmp Designer там же, оно позволяет создавать профили, и даже обмерять тестовым сигналом усилители и копировать их звучание.

    2. Набор плагинов Guitarix LV2. Есть в репах дистрибутивов. Проект известнейший с большой историей. Есть отдельное приложение Guitarix, но я рекомендую использовать именно DAW систему (Ardour, Reaper) и плагины в ней, потому что DAW системы эффективнее работают со звуковой картой в вопросе минимальной задержки.

    3. Нейро-плагины. Есть как минимум три свободных проекта плагинов гитарного усилителя, с моделированием нейросетями. Это AIDA-X, SmartGuitarAmp, Chamelon.

    4. Общие наборы плагинов LV2, VST для Linux. Могут понадобиться для дополнительных эффектов/обработки.

    5. Свободные и проприетарные плагины можно искать здесь: https://linuxdaw.org/ https://linuxmusic.rocks/

    6. Хоть исходники и не доступны (я не нахожу), но очень интересный проект, задается схема гитарного усилителя, и она моделируется: https://nalexplugins.blogspot.com/

Список будет продолжаться, актуализироваться, исправляться…

 , , ,

James_Holden
()

Как закалялся шакал: а где можно download more ram?

Статьи — Администрирование

Вступление

Данная статья написана с целью концентрации знаний о сжатии данных оперативной памяти.

Я использую gentoo, поэтому имена пакетов будут приводиться в формате этого дистрибутива.

Подразумевается, что читающий имеет желание и возможность (одно из):

  • произвести конфигурацию и/или сборку ядра;
  • в случае отсутствия искомой функциональности в «ванильном» - найти и использовать ядро, в котором уже всё есть.

zram

zram это, если описать попроще, tmpfs со сжатием.

Необходимо включить zram в ядре: CONFIG_ZRAM=m (или =y, но настоятельно рекомендуется загружать как модуль), либо в menuconfig:

Device Drivers  --->
[*] Block devices  --->
<M>   Compressed RAM block device support

Также стоит обратить внимание на другие доступные опции конфигурации, например - метод компрессии ZRAM_BACKEND_XXX.

Важно заметить, что zram может использоваться как простое блочное устройство и как приоритетный своп, аналогично zswap (см. ниже). В случае последнего необходимо собирать модуль с опцией CONFIG_ZRAM_WRITEBACK, которая позволяет отгружать данные из zram-девайса на ФС.

Наличие zram можно проверить по наличию модуля zram (только если zram собран модулем) или существованию /dev/zramX (если модуль загружен или zram вшит в ядро).

Утилита zramctl входит в состав sys-apps/util-linux (которая, скорее всего, уже стоит в системе), с её помощью можно создать юзабельный zram диск. Описание утилиты можно прочитать на man 8 zramctl.

Одновременно может существовать несколько устройств zram, каждое со своей собственной конфигурацией (объём, путь, права, writeback итд). Для zram есть вспомогательные проекты, например sys-apps/zram-generator и sys-block/zram-init, которые упрощают конфигурацию функциональности, но использовать их не является строгой необходимостью.

Ряд полезных инструкций можно найти тут: арчвики, гентувики.

zswap

zswap представляет из себя прослойку между RAM и swap: вместо простой выгрузки неактивных страниц в своп, сначала они подвергаются компрессии и помещаются в отдельное виртуальное хранилище в оперативной памяти; затем данные страницы выгружаются в своп при нехватке RAM или заполнении данного хранилища и при выгрузке производится декомпрессия.

Необходимо включить zswap в ядре: CONFIG_ZSWAP=y, либо в menuconfig:

Memory Management options  --->
[*] Support for paging of anonymous memory (swap)  --->
[*]   Compressed cache for swap pages

Также стоит обратить внимание на другие доступные опции конфигурации, например - выбрать дефолтные метод компрессии CONFIG_ZSWAP_COMPRESSOR_DEFAULT_XXX и аллокатор CONFIG_ZSWAP_ZPOOL_DEFAULT_XXX.

Проверить сессию на наличие zswap можно несколькими способами:

  1. # dmesg | grep zswap

  2. $ grep -r . /sys/module/zswap/parameters/

Настройку параметров можно производить либо в рантайме (через манипуляцию параметров в /sys/module/zswap/parameters/max_pool_percent), либо в параметрах загрузки ядра. Доступные параметры и их расшифровка описаны здесь: https://docs.kernel.org/admin-guide/mm/zswap.html

Если используешь не очень старую версию htop, то там эта память будет учитываться в общих графах compressed и frontswap.

Если используются одновременно zswap и zram (конкректно для swap), то лучше выбрать что-то одно, особенно при использовании ZRAM_WRITEBACK.

KSM

Kernel Samepage Merging - технология дедупликации одинаковых страниц памяти. Не является непосредственно сжатием, но позволяет получить больше свободной RAM, так что пусть тут будет.

На ядро должен быть наложен патч UKSM.

Необходимо включить KSM в ядре: CONFIG_KSM=y, либо в menuconfig:

Memory Management options  --->
[*] Enable KSM for page merging

Проверить сессию на присутствие и работоспособность KSM можно через проверку в /sys, например:

$ grep -r . /sys/kernel/mm/ksm

Если ядро настроено корректно, то каталог /sys/kernel/mm/ksm должен существовать и значение /sys/kernel/mm/ksm/run быть «1».

Слияние памяти происходит только для процессов, помеченных madvise(). Так как ручками мы делать это не будем, далее стоит установить sys-process/uksmd. Данный демон работает в фоне и производит инициализацию слияния автоматически, достаточно закрепить его в системе как сервис.

Послесловие

Надеюсь, что тебе понравилось читать эту статью настолько же, насколько мне нравилось её писать.

Предложения/исправления/дополнения приветствуются.

Форумчанин @kirill_rrr произвёл бенчмарк разных наборов конфигураций zram, swap и zswap в 2023.

 , , ,

Bfgeshka
()

EasyEffects не нужно, или PipeWire для продвинутых: часть 5

Статьи — Desktop

Расширение стереоэффекта для динамиков ноутбука

Предыдущие части:

Часть 1

Часть 2

Часть 3

Часть 4

( читать дальше... )

 , ,

James_Holden
()

DNS-Over-QUIC для блокировки рекламы и не только

Статьи — Администрирование
DNS-Over-QUIC для блокировки рекламы и не только

Протокол QUIC, в отличие от UDP поддерживает шифрование и работает быстрее чем TCP, который часто используется для шифрованных DNS-запросов. Данная инструкция актуальная, так как Google все-таки начал удаление поддержки блокировщиков рекламы из своего браузера. Кроме того, она пригодится тем, кто хочет скрыть свое пребывание в Интернете от тотальной слежки.

( читать дальше... )

 , , ,

rtxtxtrx
()

Tcl/tk. GUI на SVG-виджетах. Поддержка SVG-файлов. Часть II

Статьи — Разработка

Прежде чем перейти к рассмотрению svg-файлов, хотелось бы остановиться на самом интерпретаторе tcl/tk. Отвечая на один из вопросов, заданных после публикации первой части, я написал, что рассматриваемый проект успешно работает и на tcl/tk версии 9.0.1.

Мне захотелось, чтобы вместе с публикацией второй части статьи был выложен и дистрибутив графического интерпретатора tclexecomp на базе девятой версии tcl/tk. Однако выпуск версии tclexecomp для tcl/tk-9 задерживается и было решено собрать дистрибутив самостоятельно. Уже имея опыт сборки проекта Bawt для tcl/tk-9.0.1, удалось после нескольких дней работы собрать и дистрибутив tclexecomp с tcl/tk-9.0.1, с включёнными в него пакетами проекта svgwidgets. И всё было замечательно, все тестовые примеры работали. Но тут случайно мною была на клавиатуре нажата клавиша Backspace и произошел крах интерпретатора. После проверки остальных клавиш выяснилось, что аналогичный эффект происходит при нажатии клавиш Left и Right. Сначала я подумал, что это мои косяки, но решил проверить работу этих клавиш на виджетах text, entry и ttk::entry, в которых для редактирования текста используются эти клавиши. И тут выяснялось, что нажатие на эти клавиши приводит к краху интерпретатора wish9.0, т. е. собранная версия графического интерпретатора tclexecomp здесь ни при чём.

Честно говоря, я не стал проверять работу этих клавиш на первой версии tcl/tk-9.0.0. Первым порывом было написать письмо разработчикам tcl/tk, но прежде я решил посмотреть, как идут дела у разработчиков с подготовкой очередного релиза. И здесь меня ждал приятный сюрприз. В файле изменений для tk-9.0.2 присутствует такая запись: Backspace crashes 9.0 interpreter on FreeBSD.

Это было то, с чем столкнулся я. Правда здесь речь идёт о FreeBSD, а у меня Linux, но я решил проверить. Проверка дала положительный результат и больше проблем с клавиатурой я не наблюдал. Собранный интерпретатор со всеми пакетами svgwidgets под именем tclexecomp64_902_Lin64 добавлен в проект TkSVGWidgets на GitHub-е в папку tclexecomp902. Всем, кто будет тестировать проект, необходимо его скачать заново с GitHub. А теперь возвращаемся к основному материалу.

SVG-файлы представляют собой текстовые файлы с расширением .svg, содержащие xml-код, описывающий изображения в виде геометрических примитивов: линий, кривых, фигур, текста и т.д.

В tk-9.0 добавлена частичная поддержка svg-графики, которая позволяет использовать svg-иконки в GUI. Для этого сначала создается изображение:

image create photo [<идентификатор изображения>] [-format {svg [-scale <масштабирование> | -scaletowidth <ширина> | -scaletoheight <высота>]} 

Данный оператор всегда возвращает идентификатор изображения. Параметр scale задает масштаб изображения относительно исходного. Параметр scaletowidth указывает требуемую ширину изображения. Пропорционально изменению ширины будет установлена и высота изображения. Аналогично работает и параметр scaletoheight. Менять размер изображения можно и командой configure:

<идентификатор изображения> configure -format {svg [-scale <масштабирование> | -scaletowidth <ширина> | -scaletoheight <высота>] 

Помимо того, что нельзя изменить соотношение сторон, также есть проблемы с отображением градиентной заливки и стрелок.

Это классика, а мы говорим об svg-виджетах и здесь этих проблем нет.

Для демонстрации запустим интерпретатор tclexecomp64_902_Lin64.

После запуска интерпретатора и загрузки пакета svg2can достаточно выполнить несколько команд, чтобы на холсте появилось изображение из файла svgimages/Action_launch.svg:

#Загружвем пакет svg2can
package require svg2can
#Создаем окно проекта
toplevel .t -background cyan
#Геометрия окна
wm geometry .t  380x640
#Создаем холст tkp::canvas
tkp::canvas .t.c -bg yellow -width 500 -height 450
#Размещаем холст в окне
pack .t.c -fill both -expand 1
#Размещаем на холсте картинку из svg-файла
set idsvg [svg2can::SVGFileToCanvas .t.c svgimages/Action_launch.svg]
winfo class .win.c
#Координаты картинки на холсте
set coordsid [.t.c bbox $idsvg]

В данном примере для отображения на холсте картинки используется команда svg2can::SVGFileToCanvas из пакета svg2can, которая имеет следующий формат:

svg2can::SVGFileToCanvas <tkp-холст> <путь к svg-файлу>

Xml-код картинки необязательно должен храниться в файле, его можно задавать явно в виде константы или как значение tcl-переменной. В этом случае используется команда следующего вида:

svg2can::SVGXmlToCanvas <path-холст> <xml-код>

Команды svg2can::SVGFileToCanvas и svg2can::SVGXmlToCanvas при успешном выполнении возвращают идентификатор группы (далее просто группа), в которой сгруппированы все элементы картинки. Чтобы получить все идентификаторы элементов картинки, достаточно выполнить команду:

<холст с картинкой> children <идентификатор группы>

Очевидно, что рисунок на холсте должен располагаться в определенном месте и иметь определенные размеры. Для перемещения картинки в соответствующие координаты и установления требуемого размера служит команда копирования группы:

svg2can::copy <холст с картинкой> <холст назначения> <группа> [<параметры>]

Эта команда копирует заданную картинку (<холст с картинкой> и <группа>) на указанный <холст назначения> и возвращает идентификатор группы на холсте назначения. Естественно, копирование может производиться и в рамках одного холста. После копирования оригинал может быть (если он больше не потребуется) уничтожен:

<холст с картинкой> delete <идентификатор группы>

Координаты клона на заданном холсте задаются списком параметров:

-x <координата по оси X>
-y <координата по оси Y>
-width <ширина картинки>
-height <высота картинки>

Для примера клонируем нашу картинку, при этом ширину картинки увеличим в полтора раза, а высоту увеличим в три раза и расположим справа от эталона. Размеры оригинала определим, выполнив команду bbox:

#Вычисляем координаты орининала
lassign [.t.c bbox $idsvg] x0 y0 x1 y1
#Создаем клон ракеты
::svg2can::copy .t.c .t.c $idsvg -x $x1 -y $y1 -width  [expr {($x1 - $x0) * 1.5}] -height [expr {($y1 - $y0) * 3}]

Результат можно увидеть в центре скриншота, в начале статьи.

Итак, svg-изображение характеризуется двумя параметрами: именем холста, на котором оно размещено, и номером группы на этом холсте. SVG-изображения, так же как и классические изображения (image) могут использоваться как иконки в виджетах классов ibutton и cbutton. В классе cbutton иконки применимы к типам rect и square. Классические иконки задаются их идентификаторами, а svg-изображения задаются парой (списком)

-image | -isvg [list <svg-холст> <идентификатор группы>] 

или

-image | -isvg "<svg-холст> <идентификатор группы>"

Ниже представлен код, который добавляет кнопку «Старт» с иконкой в виде ракеты:

#Загружаем пакет svgwidgets
package require svgwidgets
#Устанавливаем высоту холста
.t.c configure -height 520
#Создаём кнопку «Старт» 
set idlauch [cbutton new .t.b2 -text Старт -image ".t.c $idsvg" -rx 10 -strokewidth 3]
#Устанавливает характеристика шрифта
$idlauch config -fontsize 29 -textfill red -textstroke yellow -fontweight bold
#Размещаем иконку в окне
pack  [$idlauch canvas] -fill both -expand 1 -padx 2c -pady 5m

Можно «поиграться» геометрией главного окна (например, перемещая правый нижний угол окна) и посмотреть как будет видоизменяться иконка с ракетой.

На Linux в каталоге /usr/share/icons можно найти много svg-файлов. Для их просмотра были написаны тестовые утилиты svgTestOrigToSVGWIDGET_Pack.tcl и svgtesttocan.tcl, которые находятся в каталоге examples.

К svg-изображениям можно применять различные трансформации (перемещение, масштабирование, поворот, смещение по осям X и Y). Увидеть, как это работает, можно, запустив утилиту examples/svgFileToCan.tcl. Результат работы этого скрипта можно видеть в правой нижней четверти скриншота в начале статьи.

Для комплексной демонстрации возможностей проекта svgwidgets в хранилище проекта на GitHub-е добавлен каталог examples/CryptoArmPKCS_Test, в котором лежит демонстрационный код криптографической утилиты cryptoatmpkcs_svg для работы с электронной подписью. Для ее запуска следует воспользоваться интерпретатором tclexecomp64_902_Lin64:

$tclexecomp64_902_Lin64 examples/CryptoArmPKCS_Test/mainguipkcs_svg.tcl

Стартовую страницу этой утилиты можно видеть в правой верхней четверти скриншота в начале статьи.

 , , , ,

TclTk
()

Переезжаем на Fish

Статьи — Администрирование
Переезжаем на Fish

Fish — это современный командный интерпретатор, недавно переписанный на Rust, который не требует особой настройки, поддерживая из коробки автодополнение и подсветку синтаксиса. Является лучшим Unix SHELL по версии Slant.

( читать дальше... )

 , ,

rtxtxtrx
()

Получаем OTP-коды для авторизации через терминал

Статьи — Администрирование
Получаем OTP-коды для авторизации через терминал

TOTP (Time-Based One-Time Password) или просто OTP — это алгоритм для генерации одноразовых паролей, который не требует сторонних сервисов. Он работает локально на основе общего секретного ключа и текущего времени. Например, GitHub с 2024 года использует двухфакторную авторизацию (ввод пароля + ввод какого-то кода) только через OTP (в России авторизация через SMS недоступна).

( читать дальше... )

 , ,

rtxtxtrx
()

Настраиваем для браузера несколько выходов на внешние прокси и удобно переключаемся между ними

Статьи — Desktop

Почему не VPN

Как выяснилось, очень неудобно когда включив на компьютере VPN оказывается, что половина сайтов отказываются работать там, куда он ведёт. В голову пришла идея, что если бы в каждой вкладке браузера были свои уникальные параметры соединения, это был бы идеальный вариант.

Суть решения

Дело в том, что мне нравятся Google Chrome и Microsoft Edge, но вот беда, нормально работать с прокси они не умеют. Поэтому, проведя некоторые изыскания, я смог создать рабочий конфиг на основе privoxy и плагина для упомянутых браузеров Proxy SwitchyOmega.

privoxy обеспечивает socks5-соединение, а Proxy SwitchyOmega даёт возможность менять параметры подключения в любой момент, в том числе – автоматически по выбранному шаблону, при этом может работать с несколькими внешними прокси.

( читать дальше... )

 , , , ,

unclestephen
()
: nnn и иконки (17 комментариев)

Tcl/tk. GUI на SVG-виджетах. Часть I

Статьи — Разработка

Хочу представить свой пет-проект . Создан он в недрах tсl/tk, который, по моему мнению, многие недооценивают. В статье речь пойдет не столько о tcl (хотя все примеры и сам проект написаны именно на нем), а сколько о tk.

( читать дальше... )

 , , , ,

TclTk
()

Что почитать C++ программистам

Статьи — Разработка

Предлагаю вашему вниманию две бесплатные электронные книги. Первая написана мной: «Вредные советы для C++ программистов» (PDF-формат). Это переработанный под печать вариант «60 антипаттернов для С++ программиста». Если вы уже читали 60 антипаттернов, то, пожалуй, читать новый вариант смысла нет. А если нет, то рекомендую — это и возможность местами улыбнуться, и польза.

Теперь про вторую книгу: «Путеводитель C++ программиста по неопределённому поведению». Здесь я был редактором, автор: Дмитрий Свиридкин. Книга опубликована частями, ссылки на которые приведены ниже. Сейчас Дмитрий перерабатывает материал под книжное издание, но это отдельная история.

  1. Часть 1: предисловие, что такое неопределённое поведение, и как оно проявляется, сужающие преобразования и неявное приведение типов.
  2. Часть 2: переполнение целых знаковых чисел, числа с плавающей точкой, integer promotion, char и знаковое расширение.
  3. Часть 3: висячие ссылки, string_view, синтаксический сахар с ложкой дёгтя (range-based for), self-reference, std::vector и инвалидация ссылок.
  4. Часть 4: списки захвата лямбда-функций, кортежи, внезапная мутабельность, неявные ссылки, use-after-move, lifetime extension.
  5. Часть 5: Most Vexing Parse, неконстантные константы, семантика перемещения, std::enable_if_t против std::void_t, забытый return.
  6. Часть 6: эллипсис и функции, operator [], iostreams (счастливой отладки!), оператор запятая, function-try-block, типы «нулевого» размера.
  7. Часть 7: NULL-терминированные строки, std::shared_ptr, (не)явное приведение типов, как передать стандартную функцию и ничего не сломать.
  8. Часть 8: бесконечные циклы и проблема остановки, рекурсия, ложный noexcept, переполнение буфера.
  9. Часть 9: (N)RVO vs RAII, разыменование нулевых указателей, static initialization order fiasco, static inline, нарушение ODR, зарезервированные имена.
  10. Часть 10: тривиальные типы и ABI, неинициализированные переменные, С++20 unbounded ranges, невиртуальные виртуальные функции, VLA.
  11. Часть 11: невалидные указатели, placement new для массивов, data race, повторный захват mutex, сигнало(не)безопасность, как сделать всё правильно и уйти в deadlock.
  12. Часть 12: std::vector::reserve и std::vector::resize, невыровненные ссылки, время жизни и смерти, статический анализ и UB, заключение.

И последнее: если пропустили, в конце 2024 года, как всегда, вышла подборка про самые интересные из найденных нами багов: Топ-10 ошибок в C и C++ проектах за 2024 год.

 ,

Andrey_Karpov_2020
()

EasyEffects не нужно, или PipeWire для продвинутых: часть 4

Статьи — Desktop

Спатиалайзер для наушников

Вы когда-нибудь задумывались, чем прослушивание музыки через наушники, отличается от прослушивания музыки через колонки? Основное отличие – когда мы в наушниках, то левое ухо слышит только звук левого наушника (левый стерео канал), и совсем не слышит звук правого. И наоборот. Когда мы слушаем колонки, то оба уха слышат обе колонки, но по-разному. Из-за этого простого факта, восприятие стерео-сцены в наушниках и через колонки, радикально отличается.

И возникает существенная проблема – а на что должна быть расчитана запись, на прослушивание в наушниках, или в колонках? Индустрия выбирает колонки, как основной источник звука, и все делается в расчете на них. А в наушниках мы будем слышать неправильное стерео, сильно искаженное.

Но у нас же есть pipewire, поэтому не беда, сейчас мы это исправим! В этой и следующей статье, мы заставим звучать наушники как колонки, а колонки как наушники (ну почти)!

( читать дальше... )

 , ,

James_Holden
()

image viewer simply на bash

Статьи — Разработка
image viewer simply на bash

Хочу представить простенький просмотрщик изображений на bash. Накропал его сам и может он не имеет практического применения,но все же. Конечно там возможно много сделать лучше и проще. Как говорится как есть))

#!/bin/bash

export tmp_num="/tmp/numbegin"
[ ! -f "$tmp_num" ] && echo "1" > "$tmp_num"
export file_image="/tmp/file_image" 
[ ! -f "$file_image" ] &&  find / -xdev  -type f -name "*.png" -or -name "*.svg"  -or -name "*.jpg"  >  /tmp/file_image  
 
export col_image=$(cat $file_image | wc -l)
export max_stroke=$(grep -c $ "$file_image" | (read a; echo $(( ($a/300)*300+1 ));))
export FILE_PATH=$(realpath "$0") 
export catnum=$(cat "$tmp_num")
  function IMAGE_FILE () { 
begin="$1" 
begin="$catnum"
IFS=$'\n'
i="$begin"  
 
for file_name in $(sed -n "${begin},$((${begin} + 299))p" $file_image) 
  do
#size=$(exiv2 $file_name 2>/dev/null | awk ' NR==2 {printf("%.2f", sum ($4 / 1024)); print "Kb"}; NR==4 {print $4 "x" $6 "px"}' | tr '\n' ' ') --text=\"$size\"yad  --undecorated --image=\"$file_name\" --text=\"$file_name\" --selectable-labels  
 if (( "$i % 30" == 0 )) && [[ "$i" -le $((${begin} + 299-30)) ]] ; then num="</hbox><hbox>"; else num=""; fi
  echo "<button tooltip-text=\"$i $file_name\"><input file>\"$file_name\"</input><action>xdg-open  \"$file_name\"</action><height>30</height><width>30</width></button> $num"
  i=$((i + 1))
done
}
export -f IMAGE_FILE 
 
export MAIN_DIALOG_IMAGE='<window window-position="1" title="Галерея  bash"><vbox><hbox>'`IMAGE_FILE $begin`'</hbox><hbox space-expand="true" space-fill="true">
<button label="start">
<action>echo "1" > "$tmp_num"</action>
<action>$FILE_PATH $begin &</action></button>

<button label="'"$catnum"' prev">
<action>if [ "$catnum" -gt 1 ]; then begin=$(($catnum - 300)); echo "$begin" > "$tmp_num"; else begin=1; echo "1" > "$tmp_num"; fi</action>
<action>$FILE_PATH $begin &</action></button>

<button label="next '"$catnum"' - '"$(( $catnum + 299 ))=$col_image"'">
<action>if [[ "$catnum" -lt "$max_stroke" ]]; then begin=$(($catnum + 300)); echo "$begin" > "$tmp_num"; else begin=1; echo "1" > "$tmp_num"; fi</action>
<action>$FILE_PATH $begin &</action></button>
</hbox>
</vbox></window>'

gtkdialog --program=MAIN_DIALOG_IMAGE &
 
 PID_SUM=$(ps | grep "MAIN_DIALOG_IMAGE" | grep -v grep | awk '{print $1}' | wc -l) 
  PID=$(ps -eo pid,cmd | grep "MAIN_DIALOG_IMAGE" | grep -v grep | awk '{print $1}' | head -1)
  [ "$PID_SUM" -gt 1 ] && kill $PID
[ "$catnum" -ge "$max_stroke" ] && echo "1" > "$tmp_num" && $FILE_PATH $begin &

Хотел чтобы список был больше,но long arg list не дал это сделать. ps Не имею ни какого образования в программировании.

 , ,

nik120s
()

RSS подписка на новые темы