From 19d0946e06fa5fd6e28406700495fcb5658435c0 Mon Sep 17 00:00:00 2001 From: Dominik Polakovics Date: Wed, 22 Oct 2025 09:52:40 +0200 Subject: [PATCH] feat: add sops to vim --- .../modules/development/nvim/config/sops.lua | 144 ++++++++++++++++++ hosts/nb/modules/development/nvim/default.nix | 1 + 2 files changed, 145 insertions(+) create mode 100644 hosts/nb/modules/development/nvim/config/sops.lua diff --git a/hosts/nb/modules/development/nvim/config/sops.lua b/hosts/nb/modules/development/nvim/config/sops.lua new file mode 100644 index 0000000..6d95957 --- /dev/null +++ b/hosts/nb/modules/development/nvim/config/sops.lua @@ -0,0 +1,144 @@ +-- SOPS integration for automatic encryption/decryption of secrets files +-- This module sets up autocmds to handle .secrets.yaml files transparently + +local sops_group = vim.api.nvim_create_augroup("SopsEncryption", { clear = true }) + +-- Pattern matching for secrets files +local secrets_patterns = { + "*/secrets.yaml", + "*secrets*.yaml", +} + +-- Helper function to check if file matches secrets pattern +local function is_secrets_file(filepath) + for _, pattern in ipairs(secrets_patterns) do + if vim.fn.match(filepath, vim.fn.glob2regpat(pattern)) ~= -1 then + return true + end + end + return false +end + +-- Decrypt file after reading +vim.api.nvim_create_autocmd("BufReadPost", { + group = sops_group, + pattern = secrets_patterns, + callback = function(args) + local filepath = vim.fn.expand("%:p") + + -- Only decrypt if file exists and has content + if vim.fn.filereadable(filepath) == 1 and vim.fn.getfsize(filepath) > 0 then + -- Save cursor position + local cursor_pos = vim.api.nvim_win_get_cursor(0) + + -- Decrypt file content + local result = vim.fn.system("sops --decrypt " .. vim.fn.shellescape(filepath)) + + if vim.v.shell_error == 0 then + -- Replace buffer content with decrypted content + vim.api.nvim_buf_set_lines(0, 0, -1, false, vim.split(result, "\n")) + + -- Mark buffer as not modified (since we just loaded it) + vim.bo.modified = false + + -- Restore cursor position + pcall(vim.api.nvim_win_set_cursor, 0, cursor_pos) + + -- Disable swap, backup, and undo files for security + vim.bo.swapfile = false + vim.bo.backup = false + vim.bo.writebackup = false + vim.bo.undofile = false + + -- Set filetype to yaml if not already set + if vim.bo.filetype == "" then + vim.bo.filetype = "yaml" + end + + vim.notify("SOPS: File decrypted successfully", vim.log.levels.INFO) + else + vim.notify("SOPS: Failed to decrypt file: " .. result, vim.log.levels.ERROR) + end + end + end, +}) + +-- Encrypt file before writing +vim.api.nvim_create_autocmd("BufWritePre", { + group = sops_group, + pattern = secrets_patterns, + callback = function(args) + local filepath = vim.fn.expand("%:p") + + if is_secrets_file(filepath) then + -- Get current buffer content + local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false) + local content = table.concat(lines, "\n") + + -- Encrypt content using SOPS + local encrypted = vim.fn.system("sops --encrypt /dev/stdin", content) + + if vim.v.shell_error == 0 then + -- Write encrypted content directly to file + local file = io.open(filepath, "w") + if file then + file:write(encrypted) + file:close() + + -- Mark buffer as saved (prevent Vim from writing again) + vim.bo.modified = false + + vim.notify("SOPS: File encrypted and saved successfully", vim.log.levels.INFO) + else + vim.notify("SOPS: Failed to write encrypted file", vim.log.levels.ERROR) + end + else + vim.notify("SOPS: Failed to encrypt file: " .. encrypted, vim.log.levels.ERROR) + -- Prevent write on encryption failure + return true + end + + -- Prevent default write behavior since we handled it + return true + end + end, +}) + +-- Re-decrypt after writing to show plaintext in buffer +vim.api.nvim_create_autocmd("BufWritePost", { + group = sops_group, + pattern = secrets_patterns, + callback = function(args) + local filepath = vim.fn.expand("%:p") + + if is_secrets_file(filepath) and vim.fn.filereadable(filepath) == 1 then + -- Decrypt and reload buffer content + local result = vim.fn.system("sops --decrypt " .. vim.fn.shellescape(filepath)) + + if vim.v.shell_error == 0 then + -- Save cursor position + local cursor_pos = vim.api.nvim_win_get_cursor(0) + + -- Replace buffer with decrypted content + vim.api.nvim_buf_set_lines(0, 0, -1, false, vim.split(result, "\n")) + + -- Mark as not modified + vim.bo.modified = false + + -- Restore cursor position + pcall(vim.api.nvim_win_set_cursor, 0, cursor_pos) + end + end + end, +}) + +-- Warn when leaving a secrets buffer with unsaved changes +vim.api.nvim_create_autocmd("BufLeave", { + group = sops_group, + pattern = secrets_patterns, + callback = function(args) + if vim.bo.modified then + vim.notify("Warning: Unsaved changes in secrets file!", vim.log.levels.WARN) + end + end, +}) diff --git a/hosts/nb/modules/development/nvim/default.nix b/hosts/nb/modules/development/nvim/default.nix index 233414f..17f7d5c 100644 --- a/hosts/nb/modules/development/nvim/default.nix +++ b/hosts/nb/modules/development/nvim/default.nix @@ -102,6 +102,7 @@ in "utils" "bufferline" "which-key" + "sops" ]); in '' lua << EOF