15 Commits

36 changed files with 3247 additions and 1 deletions

View File

@@ -0,0 +1,145 @@
[Midnight-Commander]
verbose=true
shell_patterns=true
auto_save_setup=true
preallocate_space=false
auto_menu=false
use_internal_view=true
use_internal_edit=false
clear_before_exec=true
confirm_delete=true
confirm_overwrite=true
confirm_execute=false
confirm_history_cleanup=true
confirm_exit=false
confirm_directory_hotlist_delete=false
confirm_view_dir=false
safe_delete=false
safe_overwrite=false
use_8th_bit_as_meta=false
mouse_move_pages_viewer=true
mouse_close_dialog=false
fast_refresh=false
drop_menus=false
wrap_mode=true
old_esc_mode=true
cd_symlinks=true
show_all_if_ambiguous=false
use_file_to_guess_type=true
alternate_plus_minus=false
only_leading_plus_minus=true
show_output_starts_shell=false
xtree_mode=false
file_op_compute_totals=true
classic_progressbar=true
use_netrc=true
ftpfs_always_use_proxy=false
ftpfs_use_passive_connections=true
ftpfs_use_passive_connections_over_proxy=false
ftpfs_use_unix_list_options=true
ftpfs_first_cd_then_ls=true
ignore_ftp_chattr_errors=true
editor_fill_tabs_with_spaces=false
editor_return_does_auto_indent=false
editor_backspace_through_tabs=false
editor_fake_half_tabs=true
editor_option_save_position=true
editor_option_auto_para_formatting=false
editor_option_typewriter_wrap=false
editor_edit_confirm_save=true
editor_syntax_highlighting=true
editor_persistent_selections=true
editor_drop_selection_on_copy=true
editor_cursor_beyond_eol=false
editor_cursor_after_inserted_block=false
editor_visible_tabs=true
editor_visible_spaces=true
editor_line_state=false
editor_simple_statusbar=false
editor_check_new_line=false
editor_show_right_margin=false
editor_group_undo=true
editor_state_full_filename=true
editor_ask_filename_before_edit=false
nice_rotating_dash=true
shadows=true
mcview_remember_file_position=false
auto_fill_mkdir_name=true
copymove_persistent_attr=true
pause_after_run=1
mouse_repeat_rate=100
double_click_speed=250
old_esc_mode_timeout=1000000
max_dirt_limit=10
num_history_items_recorded=60
vfs_timeout=60
ftpfs_directory_timeout=900
ftpfs_retry_seconds=30
shell_directory_timeout=900
editor_tab_spacing=8
editor_word_wrap_line_length=72
editor_option_save_mode=0
editor_backup_extension=~
editor_filesize_threshold=64M
editor_stop_format_chars=-+*\\,.;:&>
mcview_eof=
skin=default
[Layout]
output_lines=0
left_panel_size=73
top_panel_size=0
message_visible=true
keybar_visible=true
xterm_title=true
command_prompt=true
menubar_visible=true
free_space=true
horizontal_split=false
vertical_equal=true
horizontal_equal=true
[Misc]
timeformat_recent=%-d %b, %H:%M
timeformat_old=%-d %b %Y
ftp_proxy_host=gate
ftpfs_password=anonymous@
display_codepage=UTF-8
source_codepage=Other_8_bit
autodetect_codeset=
spell_language=en
clipboard_store=
clipboard_paste=
[Colors]
base_color=
xterm-256color=
color_terminals=
tmux-256color=
[Panels]
show_mini_info=true
kilobyte_si=false
mix_all_files=false
show_backups=true
show_dot_files=true
fast_reload=false
fast_reload_msg_shown=false
mark_moves_down=true
reverse_files_only=true
auto_save_setup_panels=false
navigate_with_arrows=false
panel_scroll_pages=true
panel_scroll_center=false
mouse_move_pages=true
filetype_mode=true
permission_mode=false
torben_fj_mode=false
quick_search_mode=2
select_flags=6
[Panelize]
Modyfikowane pliki git=git ls-files --modified
Znajdź programy SUID i SGID=find . \\( \\( -perm -04000 -a -perm /011 \\) -o \\( -perm -02000 -a -perm /01 \\) \\) -print
Znajdź odrzuty po łataniu=find . -name \\*.rej -print
Znajdź pliki *.orig po łataniu=find . -name \\*.orig -print

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,44 @@
-- ======================================================================
-- init.lua — Neovim configuration with modular structure
-- ======================================================================
-- Set leader keys before lazy.nvim
vim.g.mapleader = " "
vim.g.maplocalleader = " "
-- Bootstrap 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", "--branch=stable",
lazypath,
})
end
vim.opt.rtp:prepend(lazypath)
-- Load configuration modules
require("options")
require("keymaps")
-- Load plugins
require("lazy").setup("plugins", {
change_detection = { notify = false },
})
-- Load utilities and create commands
local utils = require("utils")
vim.api.nvim_create_user_command("ShowFileNameRight", utils.show_filename_right, {})
-- MCP Server Integration - Start socket if not already listening
if vim.fn.serverstart then
local socket_path = "/tmp/nvim"
-- Only start server if not already listening
if vim.v.servername == "" or vim.v.servername ~= socket_path then
-- Try to start server, ignore if address already in use
local ok, err = pcall(vim.fn.serverstart, socket_path)
if not ok and not string.match(err or "", "address already in use") then
vim.notify("Failed to start MCP server: " .. tostring(err), vim.log.levels.WARN)
end
end
end

View File

@@ -0,0 +1,13 @@
{
"coc.nvim": { "branch": "release", "commit": "c89ed8d5c393c60e0baf796f097598a8da14816d" },
"lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" },
"nerdtree": { "branch": "master", "commit": "690d061b591525890f1471c6675bcb5bdc8cdff9" },
"nvim-dap": { "branch": "master", "commit": "818cd8787a77a97703eb1d9090543a374f79a9ac" },
"nvim-dap-ui": { "branch": "master", "commit": "cf91d5e2d07c72903d052f5207511bf7ecdb7122" },
"nvim-dap-virtual-text": { "branch": "master", "commit": "fbdb48c2ed45f4a8293d0d483f7730d24467ccb6" },
"nvim-nio": { "branch": "master", "commit": "21f5324bfac14e22ba26553caf69ec76ae8a7662" },
"vim-commentary": { "branch": "master", "commit": "64a654ef4a20db1727938338310209b6a63f60c9" },
"vim-fugitive": { "branch": "master", "commit": "61b51c09b7c9ce04e821f6cf76ea4f6f903e3cf4" },
"vim-oscyank": { "branch": "main", "commit": "d67d76b2f19b868b70a1cf33a779d71dc092cb30" },
"vim-slime": { "branch": "main", "commit": "3fb77a9d1d3dd3abfbdbd4840eb20947f39f688b" }
}

View File

@@ -0,0 +1,154 @@
-- hazard3_dap.lua — Hazard3 helpers for nvim-dap (GDB DAP)
local M = {}
local disasm_buf ---@type integer|nil
local function ensure_disasm_buf()
if disasm_buf and vim.api.nvim_buf_is_valid(disasm_buf) then
return disasm_buf
end
disasm_buf = vim.api.nvim_create_buf(false, true)
pcall(vim.api.nvim_buf_set_name, disasm_buf, "[DAP Disassembly]")
vim.bo[disasm_buf].buftype = "nofile"
vim.bo[disasm_buf].bufhidden = "hide"
vim.bo[disasm_buf].swapfile = false
vim.bo[disasm_buf].filetype = "asm"
vim.bo[disasm_buf].modifiable = true
vim.api.nvim_buf_set_lines(disasm_buf, 0, -1, false, { "(DAP) Disassembly" })
vim.bo[disasm_buf].modifiable = false
return disasm_buf
end
local function show_buf(buf)
local wins = vim.fn.win_findbuf(buf)
if wins and #wins > 0 then
vim.api.nvim_set_current_win(wins[1])
vim.wo.cursorline = true
return
end
vim.cmd("vsplit")
vim.api.nvim_set_current_buf(buf)
vim.wo.cursorline = true
end
local function update_disassembly(buf, opts)
opts = opts or {}
local count = opts.count or 80
local before = opts.before or 20
local ok, dap = pcall(require, "dap")
if not ok then
return
end
local session = dap.session()
if not session then
return
end
session:request("stackTrace", { threadId = 1, startFrame = 0, levels = 1 }, function(err, resp)
if err then
vim.notify("DAP disasm: stackTrace failed: " .. tostring(err), vim.log.levels.ERROR)
return
end
local frames = resp and resp.stackFrames or {}
if #frames == 0 then
vim.notify("DAP disasm: no stack frames", vim.log.levels.WARN)
return
end
local frame = frames[1]
local memref = frame.instructionPointerReference
if memref == nil or memref == "" then
vim.notify("DAP disasm: no instructionPointerReference", vim.log.levels.WARN)
return
end
session:request("disassemble", {
memoryReference = memref,
instructionOffset = -before,
instructionCount = count,
resolveSymbols = true,
}, function(err2, resp2)
if err2 then
vim.notify("DAP disasm: disassemble failed: " .. tostring(err2), vim.log.levels.ERROR)
return
end
local insts = resp2 and resp2.instructions or {}
local lines = {}
lines[#lines + 1] = "memref: " .. memref
for _, i in ipairs(insts) do
local addr = i.address or "?"
local ins = i.instruction or ""
local mark = (addr == memref) and "=> " or " "
lines[#lines + 1] = mark .. addr .. " " .. ins
end
if not vim.api.nvim_buf_is_valid(buf) then
return
end
vim.bo[buf].modifiable = true
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
vim.bo[buf].modifiable = false
local pc_line
for idx, line in ipairs(lines) do
if line:sub(1, 3) == "=> " then
pc_line = idx
break
end
end
if pc_line then
local wins = vim.fn.win_findbuf(buf)
if wins and #wins > 0 then
for _, win in ipairs(wins) do
pcall(vim.api.nvim_win_set_cursor, win, { pc_line, 0 })
vim.wo[win].cursorline = true
end
end
end
end)
end)
end
function M.open_disassembly(opts)
local buf = ensure_disasm_buf()
local cur_win = vim.api.nvim_get_current_win()
show_buf(buf)
update_disassembly(buf, opts)
if opts and opts.keep_focus then
pcall(vim.api.nvim_set_current_win, cur_win)
end
end
function M.setup()
local ok, dap = pcall(require, "dap")
if not ok then
return
end
if vim.api.nvim_create_user_command then
pcall(vim.api.nvim_create_user_command, "DapDisassembly", function()
M.open_disassembly()
end, {})
end
dap.listeners.after.event_stopped["hazard3_disasm"] = function()
if disasm_buf and vim.api.nvim_buf_is_valid(disasm_buf) then
update_disassembly(disasm_buf, { keep_focus = true })
end
end
end
return M

View File

@@ -0,0 +1,107 @@
-- ======================================================================
-- keymaps.lua — Key mappings
-- ======================================================================
local map = vim.keymap.set
-- NERDTree
map("n", "<leader>n", ":NERDTreeFocus<CR>", { silent = true, desc = "NERDTree: Focus" })
map("n", "<C-n>", ":NERDTree<CR>", { silent = true, desc = "NERDTree: Open" })
map("n", "<C-t>", ":NERDTreeToggle<CR>", { silent = true, desc = "NERDTree: Toggle" })
map("n", "<C-f>", ":NERDTreeFind<CR>", { silent = true, desc = "NERDTree: Find current file" })
-- OSC52 (vim-oscyank)
map("n", "<leader>c", "<Plug>OSCYankOperator", { desc = "OSC Yank: Operator" })
map("n", "<leader>cc", "<leader>c_", { desc = "OSC Yank: Current line" })
map("v", "<leader>c", "<Plug>OSCYankVisual", { desc = "OSC Yank: Visual selection" })
-- vim-slime
vim.g.slime_no_mappings = 1
map("x", "<C-c><C-c>", "<Plug>SlimeRegionSend", { desc = "Slime: Send region" })
map("n", "<C-c><C-c>", function()
require("utils").send_cell("^#%%")
end, { silent = true, desc = "Slime: Send cell" })
map("n", "<C-c>v", "<Plug>SlimeConfig", { desc = "Slime: Configure" })
-- Claude Code
map("n", "<leader>ac", ":ClaudeCode<CR>", { silent = true, desc = "ClaudeCode: Toggle" })
map("n", "<leader>af", ":ClaudeCodeFocus<CR>", { silent = true, desc = "ClaudeCode: Focus" })
map("v", "<leader>as", ":ClaudeCodeSend<CR>", { silent = true, desc = "ClaudeCode: Send selection" })
map("n", "<leader>ad", ":ClaudeCodeAdd ", { desc = "ClaudeCode: Add file to context" })
map("n", "<leader>aa", ":ClaudeCodeDiffAccept<CR>", { silent = true, desc = "ClaudeCode: Accept diff" })
map("n", "<leader>ar", ":ClaudeCodeDiffDeny<CR>", { silent = true, desc = "ClaudeCode: Reject diff" })
-- DAP (GDB)
local function dap_attach_or(fn)
return function(...)
local dap = require("dap")
if dap.session() then
return fn(dap, ...)
end
if vim.fn.exists(":Hazard3Attach") == 2 then
vim.cmd("Hazard3Attach")
return
end
return fn(dap, ...)
end
end
map("n", "<F5>", dap_attach_or(function(dap)
dap.continue()
end), { desc = "DAP: Continue (or Hazard3Attach)" })
map("n", "<F10>", dap_attach_or(function(dap)
dap.step_over()
end), { desc = "DAP: Step over" })
map("n", "<F11>", dap_attach_or(function(dap)
dap.step_into()
end), { desc = "DAP: Step into" })
map("n", "<F12>", dap_attach_or(function(dap)
dap.step_out()
end), { desc = "DAP: Step out" })
map("n", "<leader>db", function()
require("dap").toggle_breakpoint()
end, { desc = "DAP: Toggle breakpoint" })
map("n", "<leader>dB", function()
require("dap").set_breakpoint(vim.fn.input("Condition: "))
end, { desc = "DAP: Conditional breakpoint" })
map("n", "<leader>dr", function()
require("dap").repl.open()
end, { desc = "DAP: REPL" })
map("n", "<leader>du", function()
require("dapui").toggle()
end, { desc = "DAP: Toggle UI" })
map("n", "<leader>dR", function()
local dapui = require("dapui")
dapui.close()
dapui.open({ reset = true })
end, { desc = "DAP UI: Reset layout" })
map("n", "<leader>da", function()
require("hazard3_dap").open_disassembly()
end, { desc = "DAP: Disassembly" })
map("n", "<leader>di", dap_attach_or(function(dap)
dap.step_into({ granularity = "instruction" })
end), { desc = "DAP: Step instruction into" })
map("n", "<leader>dI", dap_attach_or(function(dap)
dap.step_over({ granularity = "instruction" })
end), { desc = "DAP: Step instruction over" })
map("n", "<leader>dx", function()
require("dap").terminate()
end, { desc = "DAP: Terminate" })
map("n", "<leader>dp", function()
require("dap").pause()
end, { desc = "DAP: Pause" })

View File

@@ -0,0 +1,51 @@
-- ======================================================================
-- options.lua — Vim options and settings
-- ======================================================================
local opt = vim.opt
-- Clipboard
opt.clipboard = "unnamedplus"
-- Editing behavior
opt.backspace = { "indent", "eol", "start" }
opt.mouse = "r"
opt.compatible = false
-- UI
opt.number = true
opt.cursorline = true
opt.showcmd = true
opt.showmode = true
opt.showmatch = true
opt.scrolloff = 10
opt.wrap = false
-- Indentation
opt.shiftwidth = 4
opt.tabstop = 4
opt.expandtab = true
-- Search
opt.incsearch = true
opt.ignorecase = true
opt.smartcase = true
opt.hlsearch = true
-- Files
opt.backup = false
opt.history = 1000
opt.tags = { "./tags;,tags;" }
-- Completion
opt.wildmenu = true
opt.wildmode = { "list", "longest" }
opt.wildignore = {
"*.docx","*.jpg","*.png","*.gif","*.pdf","*.pyc","*.exe","*.flv","*.img","*.xlsx"
}
-- Filetype detection
vim.cmd("filetype on")
vim.cmd("filetype plugin on")
vim.cmd("filetype indent on")
vim.cmd("syntax on")

View File

@@ -0,0 +1,191 @@
-- ======================================================================
-- plugins.lua — Plugin specifications for lazy.nvim
-- ======================================================================
return {
-- File explorer
{
"preservim/nerdtree",
cmd = { "NERDTree", "NERDTreeToggle", "NERDTreeFind", "NERDTreeFocus" },
},
-- Git integration
{
"tpope/vim-fugitive",
event = "VeryLazy",
},
-- Commenting
{
"tpope/vim-commentary",
event = "VeryLazy",
},
-- LSP and completion
{
"neoclide/coc.nvim",
branch = "release",
build = "npm ci",
event = "VeryLazy",
},
-- REPL integration (tmux)
{
"jpalardy/vim-slime",
init = function()
vim.g.slime_target = "tmux"
if vim.fn.exists("g:slime_python_ipython") == 0 then
vim.g.slime_python_ipython = 1
end
end,
},
-- OSC52 clipboard (remote SSH/tmux)
{
"ojroques/vim-oscyank",
event = "VimEnter",
init = function()
vim.g.oscyank_term = "tmux"
vim.g.oscyank_silent = true
end,
config = function()
local grp = vim.api.nvim_create_augroup("osc52_yank", { clear = true })
vim.api.nvim_create_autocmd("TextYankPost", {
group = grp,
callback = function()
if vim.v.event.operator == "y" and vim.v.event.regname == "" then
vim.cmd([[OSCYankRegister "]])
end
end,
})
end,
},
-- Debugging (DAP)
{
"mfussenegger/nvim-dap",
config = function()
local dap = require("dap")
dap.adapters.gdb = {
type = "executable",
command = "gdb-multiarch",
args = { "-i", "dap" },
}
local function pick_elf()
return vim.fn.input("ELF: ", vim.fn.getcwd() .. "/", "file")
end
local function normalize_target(t)
t = t or ""
if vim.trim then
t = vim.trim(t)
else
t = t:gsub("^%s+", ""):gsub("%s+$", "")
end
t = t:gsub("^target%s+extended%-remote%s+", "")
t = t:gsub("^target%s+remote%s+", "")
t = t:gsub("^extended%-remote%s+", "")
t = t:gsub("^remote%s+", "")
return t
end
local function pick_target()
local default = normalize_target(vim.env.HAZARD3_GDB_TARGET)
if default == "" then
default = "localhost:3333"
end
return normalize_target(vim.fn.input("GDB target (host:port): ", default))
end
local hazard3_attach = {
name = "Hazard3 (attach gdb-remote)",
type = "gdb",
request = "attach",
program = pick_elf,
target = pick_target,
cwd = "${workspaceFolder}",
}
local function add_cfg(ft, cfg)
dap.configurations[ft] = dap.configurations[ft] or {}
for _, existing in ipairs(dap.configurations[ft]) do
if existing.name == cfg.name then
return
end
end
table.insert(dap.configurations[ft], cfg)
end
for _, ft in ipairs({ "c", "cpp", "asm", "" }) do
add_cfg(ft, hazard3_attach)
end
pcall(function()
vim.api.nvim_create_user_command("Hazard3Attach", function()
dap.run(hazard3_attach)
end, { desc = "DAP: Hazard3 attach" })
end)
vim.fn.sign_define("DapBreakpoint", { text = "", texthl = "DiagnosticError" })
vim.fn.sign_define("DapStopped", { text = "", texthl = "DiagnosticInfo", linehl = "Visual" })
pcall(function()
require("hazard3_dap").setup()
end)
end,
},
{
"rcarriga/nvim-dap-ui",
dependencies = {
"mfussenegger/nvim-dap",
"nvim-neotest/nvim-nio",
},
config = function()
local dap = require("dap")
local dapui = require("dapui")
dapui.setup({
-- ASCII/unicode-friendly icons (no codicons required)
icons = { expanded = "v", collapsed = ">", current_frame = ">" },
controls = {
enabled = true,
element = "repl",
icons = {
pause = "||",
play = ">",
step_into = "",
step_over = "",
step_out = "",
step_back = "",
run_last = "",
terminate = "X",
disconnect = "D",
},
},
})
dap.listeners.after.event_initialized["dapui"] = function()
dapui.open()
end
dap.listeners.before.event_terminated["dapui"] = function()
dapui.close()
end
dap.listeners.before.event_exited["dapui"] = function()
dapui.close()
end
end,
},
{
"theHamsta/nvim-dap-virtual-text",
dependencies = {
"mfussenegger/nvim-dap",
},
opts = {},
},
}

View File

@@ -0,0 +1,25 @@
-- ======================================================================
-- utils.lua — Helper functions
-- ======================================================================
local M = {}
-- Show filename aligned to the right
function M.show_filename_right()
local filename = vim.fn.expand("%:p")
local columns = vim.o.columns
local space = columns - #filename - 2
if space < 0 then space = 0 end
vim.api.nvim_echo({{string.rep(" ", space) .. filename, "None"}}, false, {})
end
-- Send cell to slime (delimited by pattern, e.g., "^#%%")
function M.send_cell(pattern)
local start_line = vim.fn.search(pattern, "bnW")
if start_line ~= 0 then start_line = start_line + 1 else start_line = 1 end
local stop_line = vim.fn.search(pattern, "nW")
if stop_line ~= 0 then stop_line = stop_line - 1 else stop_line = vim.fn.line("$") end
vim.fn["slime#send_range"](start_line, stop_line)
end
return M

View File

@@ -0,0 +1,4 @@
set-option -g prefix C-a
unbind-key C-b
bind-key C-a send-prefix
set -g set-clipboard on

View File

@@ -2,9 +2,68 @@
- name: Minimal check for doc/rpc Sol host - name: Minimal check for doc/rpc Sol host
hosts: sol_rpc hosts: sol_rpc
gather_facts: true gather_facts: true
become: false become: true
tasks: tasks:
- name: Install operator packages (Debian/Ubuntu)
ansible.builtin.apt:
name:
- tmux
- mc
- git
- neovim
state: present
update_cache: true
when: ansible_facts.os_family == "Debian"
- name: Ensure root config directories exist
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: root
group: root
mode: "0755"
loop:
- /root/.config
- /root/.config/mc
- /root/.config/nvim
- /root/.config/nvim/lua
- name: Deploy tmux config (Ctrl+a prefix)
ansible.builtin.copy:
src: ../files/operator-dotfiles/tmux.conf
dest: /root/.tmux.conf
owner: root
group: root
mode: "0644"
- name: Deploy Midnight Commander config
ansible.builtin.copy:
src: "../files/operator-dotfiles/mc/{{ item.src }}"
dest: "/root/.config/mc/{{ item.dest }}"
owner: root
group: root
mode: "0644"
loop:
- { src: "ini", dest: "ini" }
- { src: "panels.ini", dest: "panels.ini" }
- name: Deploy Neovim config files
ansible.builtin.copy:
src: "../files/operator-dotfiles/nvim/{{ item.src }}"
dest: "/root/.config/nvim/{{ item.dest }}"
owner: root
group: root
mode: "0644"
loop:
- { src: "init.lua", dest: "init.lua" }
- { src: "lazy-lock.json", dest: "lazy-lock.json" }
- { src: "lua/options.lua", dest: "lua/options.lua" }
- { src: "lua/keymaps.lua", dest: "lua/keymaps.lua" }
- { src: "lua/plugins.lua", dest: "lua/plugins.lua" }
- { src: "lua/utils.lua", dest: "lua/utils.lua" }
- { src: "lua/hazard3_dap.lua", dest: "lua/hazard3_dap.lua" }
- name: Validate Ansible transport - name: Validate Ansible transport
ansible.builtin.ping: ansible.builtin.ping:

20
doc/etap-002-ssh-gitea.md Normal file
View File

@@ -0,0 +1,20 @@
# Etap 002: SSH flow dla `gitea.mpabi.pl`
Cel etapu: przejść z push przez HTTPS/token na standardowy push przez SSH do `trade/trade-iac`.
## Zakres
1. Wygenerować dedykowany klucz SSH dla Gitei (jeśli brak).
2. Dodać konfigurację hosta `gitea.mpabi.pl` w `~/.ssh/config`.
3. Dodać klucz publiczny do konta `u1` w Gitei.
4. Ustawić `origin` repo `trade-iac` na URL SSH.
5. Zweryfikować:
- `ssh -T git@gitea.mpabi.pl`,
- `git ls-remote origin`,
- `git push` bez hasła/tokena HTTP.
## Kryteria akceptacji
- SSH auth do Gitei działa dla użytkownika `git`.
- `trade-iac` używa `git@gitea.mpabi.pl:trade/trade-iac.git`.
- Push działa przez SSH.

View File

@@ -0,0 +1,26 @@
# Etap 003: Wystawienie SSH Gitei z k3s
Cel etapu: udostępnić SSH Gitei na publicznym porcie, żeby `git@gitea.mpabi.pl` trafiało do serwisu `gitea-ssh` w klastrze.
## Zakres
1. Dodać `HelmChartConfig` dla Traefika z entrypointem TCP `gitssh` na porcie `2222`.
2. Dodać `IngressRouteTCP` w namespace `gitea`, który kieruje ruch z `gitssh` do `service/gitea-ssh:22`.
3. Wdrożyć manifesty do klastra i zweryfikować:
- Traefik ma port `2222`,
- `IngressRouteTCP` jest aktywny,
- `ssh -p 2222 git@gitea.mpabi.pl` odpowiada Giteą.
## Decyzja techniczna
- Nie ruszamy hostowego `:22` (zostaje dla SSH systemu).
- Git SSH dla Gitei idzie przez `:2222`.
- Klienci Git powinni używać:
- `ssh://git@gitea.mpabi.pl:2222/trade/trade-iac.git`
- albo aliasu SSH z `Port 2222`.
## Kryteria akceptacji
- `kubectl -n kube-system get svc traefik` pokazuje port `2222/TCP`.
- `kubectl -n gitea get ingressroutetcp gitea-ssh` istnieje.
- Push/pull repo przez SSH na porcie `2222` działa.

View File

@@ -0,0 +1,30 @@
# Etap 004: Narzędzia operatora + dotfiles z laptopa
Cel etapu: doinstalować na `mevnode` podstawowe narzędzia operatorskie i wgrać konfiguracje terminalowe zgodne z tym laptopem.
## Zakres
1. Doinstalować pakiety:
- `tmux`
- `mc`
- `git`
- `neovim`
2. Ustawić `tmux` z prefiksem `Ctrl+a`.
3. Wgrać konfiguracje operatora (`root`):
- `~/.tmux.conf`
- `~/.config/mc/ini`
- `~/.config/nvim/init.lua`
- `~/.config/nvim/lua/*.lua`
- `~/.config/nvim/lazy-lock.json`
## Założenia
- Playbook działa na host `mevnode` jako `root`.
- Dotfiles są wersjonowane w repo `trade-iac` (źródło prawdy).
- `git` config globalny nie jest kopiowany, jeśli brak odpowiednika na laptopie.
## Kryteria akceptacji
- Polecenia `tmux`, `mc`, `git`, `nvim` są dostępne na `mevnode`.
- `~/.tmux.conf` zawiera prefix `C-a`.
- Pliki `~/.config/mc/ini` oraz `~/.config/nvim/*` istnieją na `mevnode`.

View File

@@ -0,0 +1,68 @@
Step 001: Auto-dodawanie klucza SSH do ssh-agent po restarcie (VPS -> baremetal)
Cel:
- Po restarcie VPS (bez recznego ssh-add) klucz prywatny do baremetala ma byc automatycznie dodany do ssh-agent.
- To jest wygodne, gdy po WG chcesz robic deploy/Ansible/ssh bez interakcji.
Wymagania:
- VPS z systemd (Debian/Ubuntu).
- Klucz prywatny jest na VPS, np. ~/.ssh/mevnode_baremetal
- Klucz NIE powinien miec passphrase (inaczej ssh-add bedzie chcial hasla i usluga oneshot padnie).
0) Plik klucza (prawa)
chmod 600 ~/.ssh/mevnode_baremetal
1) Wlacz "linger" dla usera (zeby user services startowaly po boot bez logowania)
sudo loginctl enable-linger user
2) Utworz usluge ssh-agent (systemd --user)
mkdir -p ~/.config/systemd/user
cat >~/.config/systemd/user/ssh-agent.service <<'EOF'
[Unit]
Description=SSH agent
[Service]
Type=simple
Environment=SSH_AUTH_SOCK=%t/ssh-agent.socket
ExecStart=/usr/bin/ssh-agent -D -a $SSH_AUTH_SOCK
[Install]
WantedBy=default.target
EOF
3) Utworz usluge ssh-add dla klucza do baremetala (systemd --user)
Uwaga: dodajemy ExecStartPre, zeby poczekac az socket agenta bedzie gotowy.
cat >~/.config/systemd/user/ssh-add-mevnode.service <<'EOF'
[Unit]
Description=ssh-add mevnode_baremetal
After=ssh-agent.service
Requires=ssh-agent.service
[Service]
Type=oneshot
Environment=SSH_AUTH_SOCK=%t/ssh-agent.socket
ExecStartPre=/usr/bin/sh -c 'for i in $(seq 1 50); do [ -S "$SSH_AUTH_SOCK" ] && exit 0; sleep 0.1; done; echo "SSH_AUTH_SOCK not ready: $SSH_AUTH_SOCK" >&2; exit 1'
ExecStart=/usr/bin/ssh-add %h/.ssh/mevnode_baremetal
RemainAfterExit=yes
[Install]
WantedBy=default.target
EOF
4) Reload i wlacz autostart
systemctl --user daemon-reload
systemctl --user enable --now ssh-agent.service ssh-add-mevnode.service
5) Sprawdz dzialanie
systemctl --user status ssh-agent.service ssh-add-mevnode.service --no-pager
SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/ssh-agent.socket" ssh-add -l
6) Test SSH na baremetal (po WG)
ssh user@10.66.66.1
Wskazowka:
- Alternatywa bez ssh-agent: wpis w ~/.ssh/config z IdentityFile (nie wymaga ssh-add),
ale ten plik opisuje wariant stricte "auto ssh-add po reboot".

View File

@@ -0,0 +1,88 @@
Step 001: Kolejnosc wdrozenia (najpierw WireGuard, potem odciecie publicznego SSH)
Cel: najpierw stawiamy WireGuard i testujemy polaczenie po WG, a dopiero potem ucinamy publiczne SSH.
To minimalizuje ryzyko zablokowania sie na baremetalu.
0) Pre-flight (nie zamykaj obecnej sesji SSH)
- Na baremetalu zostaw otwarta aktualna sesje SSH.
- Przygotuj drugi terminal do testow.
Sprawdz publiczny interfejs/IP routing:
ip route get 1.1.1.1
1) WireGuard na baremetalu (nie ruszaj SSH/firewall poza WG portem)
sudo apt-get update
sudo apt-get install -y wireguard
Jesli uzywasz skryptu:
sudo /usr/local/sbin/wg_serverctl.py gen-server --force
sudo /usr/local/sbin/wg_serverctl.py gen-client client1
sudo /usr/local/sbin/wg_serverctl.py add client1
sudo /usr/local/sbin/wg_serverctl.py export client1
Uruchom WG na serwerze:
sudo systemctl enable --now wg-quick@wg0
sudo wg show
ip a show wg0
2) WireGuard na kliencie i test polaczenia
Na kliencie:
sudo apt-get update
sudo apt-get install -y wireguard
sudo /usr/local/sbin/wg_client.py install ./client1.conf
sudo /usr/local/sbin/wg_client.py up
sudo /usr/local/sbin/wg_client.py status
Testy (z klienta):
ping 10.66.66.1
ssh user@10.66.66.1
Dopiero jak to dziala, idziesz dalej.
3) (Opcjonalnie teraz) UFW: wpusc WG, ale NIE odcinaj jeszcze publicznego SSH
Na baremetalu:
sudo apt-get install -y ufw
sudo ufw allow 51820/udp
sudo ufw allow 22/tcp (tymczasowo, zeby nic nie uciac przy wlaczaniu UFW)
sudo ufw enable
sudo ufw status verbose
Uwagi:
- Jesli `sudo ufw status verbose` pokazuje "Status: inactive", to reguly NIE dzialaja.
- W takim wypadku wlacz UFW bezpiecznie (zostawiajac dostep przez WG):
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 51820/udp
sudo ufw allow in on wg0 to any port 22 proto tcp
sudo ufw deny in on eth0 to any port 22 proto tcp
sudo ufw enable
sudo ufw status verbose
4) Przestawienie SSH na "tylko WireGuard"
1) Najpierw pozwol na SSH po wg0:
sudo ufw allow in on wg0 to any port 22 proto tcp
2) Potem ogranicz sshd do WG IP:
sudo sshd -t
sudoedit /etc/ssh/sshd_config
Dodaj/ustaw:
PasswordAuthentication no
PermitRootLogin no
ListenAddress 10.66.66.1
Restart i test:
sudo systemctl restart ssh
Z klienta:
ssh user@10.66.66.1
3) Jak test przeszedl: zablokuj publiczne SSH:
sudo ufw deny 22/tcp
sudo ufw status verbose
5) RPC tylko po WG (po instalacji Agave)
Docelowo:
- bind RPC/WS na 10.66.66.1 (wg0)
- UFW: allow 8899/8900 tylko "in on wg0"

View File

@@ -0,0 +1,25 @@
Step 001: Jak skopiowac klucze SSH na uzytkownika "user" uruchamiajac komendy jako root
Cel: skopiowac klucze/konfiguracje SSH z /root/.ssh do /home/user/.ssh albo tylko dodac klucz publiczny do authorized_keys.
Wariant A: Skopiuj caly katalog /root/.ssh do usera (ustaw prawa)
sudo install -d -m 700 -o user -g user /home/user/.ssh
sudo cp -a /root/.ssh/. /home/user/.ssh/
sudo chown -R user:user /home/user/.ssh
sudo chmod 700 /home/user/.ssh
sudo chmod 600 /home/user/.ssh/authorized_keys 2>/dev/null || true
sudo chmod 600 /home/user/.ssh/id_* 2>/dev/null || true
sudo chmod 644 /home/user/.ssh/*.pub 2>/dev/null || true
sudo chmod 644 /home/user/.ssh/known_hosts 2>/dev/null || true
Uwaga: kopiowanie prywatnych kluczy (id_ed25519/id_rsa) na innego uzytkownika zwieksza ryzyko. Czasem lepiej uzyc wariantu B.
Wariant B: Tylko dopisz klucz publiczny roota do authorized_keys usera
sudo install -d -m 700 -o user -g user /home/user/.ssh
sudo install -m 600 -o user -g user /dev/null /home/user/.ssh/authorized_keys
sudo cat /root/.ssh/id_ed25519.pub | sudo tee -a /home/user/.ssh/authorized_keys >/dev/null
sudo chown user:user /home/user/.ssh/authorized_keys
sudo chmod 600 /home/user/.ssh/authorized_keys

View File

@@ -0,0 +1,26 @@
Step 001: Toggle SSH na publicznym interfejsie eth0 (ON/OFF) przez UFW
Zalozenie:
- SSH po WireGuard (wg0) zostaje zawsze wlaczone.
- Publiczne SSH po eth0 wlaczasz/wylaczasz tylko regula UFW.
- UFW musi byc aktywne (status != inactive).
1) Zawsze zostaw SSH po wg0:
sudo ufw allow in on wg0 to any port 22 proto tcp
2) ON: wlacz SSH po eth0 (public):
sudo ufw allow in on eth0 to any port 22 proto tcp
sudo ufw status verbose
3) OFF: wylacz SSH po eth0 (public):
sudo ufw deny in on eth0 to any port 22 proto tcp
sudo ufw status verbose
4) (Opcjonalnie) Usuniecie konkretnej reguly:
sudo ufw status numbered
sudo ufw delete <NUMER_REGULY_DLA_22_NA_eth0>
Uwaga:
- Jesli `sudo ufw status` pokazuje "inactive", to reguly nie dzialaja. Wtedy najpierw:
sudo ufw enable

View File

@@ -0,0 +1,37 @@
Step 001: Co zrobic z plikiem po "wg_serverctl.py export"
Po stronie serwera, po wykonaniu:
sudo /etc/wireguard/wg_serverctl.py export laptop1 --endpoint 149.50.116.219:51820
Powstaje plik:
/etc/wireguard/clients/laptop1.conf
Co dalej:
1) Skopiuj plik na klienta (laptop/VPS)
Najprosciej przez scp z klienta:
scp user@149.50.116.219:/etc/wireguard/clients/laptop1.conf .
2) Zainstaluj go jako /etc/wireguard/wg0.conf na kliencie
sudo install -m 600 laptop1.conf /etc/wireguard/wg0.conf
3) Podnies WireGuard na kliencie i przetestuj
sudo wg-quick up wg0
sudo wg show
ping 10.66.66.1
ssh user@10.66.66.1
4) Autostart po restarcie (server + client)
Domyslnie bywa, ze wg-quick@wg0 jest "disabled" (szczegolnie na kliencie).
Serwer (Ubuntu/Debian, systemd):
sudo systemctl enable --now wg-quick@wg0
sudo systemctl is-enabled wg-quick@wg0
sudo systemctl status wg-quick@wg0 --no-pager
Klient (Debian/Ubuntu, systemd):
sudo systemctl enable --now wg-quick@wg0
sudo systemctl is-enabled wg-quick@wg0
Wazne:
- Dopiero jak ping i ssh po WG dzialaja, przechodzisz do odciecia publicznego SSH na serwerze.

View File

@@ -0,0 +1,116 @@
# Step 001A: WireGuard tools (server/client) + instrukcje reset
Ten step dodaje dwa skrypty pomocnicze do zarzadzania WireGuard:
- server: `scripts/wireguard/wg_serverctl.py`
- client: `scripts/wireguard/wg_client.py`
Skrypty trzymaja stan na dysku, zgodnie z konwencja:
- server keys: `/etc/wireguard/server.key`, `/etc/wireguard/server.pub`
- clients: `/etc/wireguard/clients/<name>.key|.pub|.ip|.conf`
- generated server config: `/etc/wireguard/wg0.conf`
Wymagania: `wireguard-tools` (czyli `wg`, `wg-quick`) + `systemd`.
## Instalacja (server)
Na bare metalu:
```bash
apt-get update
apt-get install -y wireguard
```
Z repo (na hoście) zainstaluj skrypt jako root-only:
```bash
install -m 700 scripts/wireguard/wg_serverctl.py /usr/local/sbin/wg_serverctl.py
```
## Instalacja (client)
Na kliencie (laptop/VPS):
```bash
apt-get update
apt-get install -y wireguard
install -m 700 scripts/wireguard/wg_client.py /usr/local/sbin/wg_client.py
```
## Typowy flow (server)
```bash
# 1) wygeneruj klucze serwera
wg_serverctl.py gen-server --force
# 2) wygeneruj klucze klienta (trzymane na serwerze w /etc/wireguard/clients)
wg_serverctl.py gen-client client1
# 3) dodaj klienta do puli, wygeneruj wg0.conf i zrestartuj wg-quick@wg0
wg_serverctl.py add client1
# 4) wyeksportuj client config (do przekazania na klienta)
wg_serverctl.py export client1
# 5) podglad
wg_serverctl.py list
wg_serverctl.py show client1
```
Plik konfiguracyjny klienta powstaje jako:
- `/etc/wireguard/clients/client1.conf`
## Typowy flow (client)
Skopiuj `client1.conf` na klienta, a potem:
```bash
wg_client.py install ./client1.conf
wg_client.py up
wg_client.py status
```
## Stop / disable / reset wg0 (Ubuntu 24.04)
Stop (zostaw config, tylko down):
```bash
systemctl stop wg-quick@wg0
```
Disable autostart:
```bash
systemctl disable wg-quick@wg0
```
Flush (pewny down + usuniecie interfejsu):
```bash
systemctl stop wg-quick@wg0
ip link del wg0 2>/dev/null || true
ip a show wg0 2>/dev/null || echo "wg0 not present"
```
Hard reset (usuniecie kluczy + konfiguracji):
```bash
systemctl stop wg-quick@wg0
systemctl disable wg-quick@wg0
ip link del wg0 2>/dev/null || true
rm -f /etc/wireguard/wg0.conf
rm -f /etc/wireguard/server.key /etc/wireguard/server.pub
rm -rf /etc/wireguard/clients
```
Reset peerow bez kasowania server key:
```bash
systemctl stop wg-quick@wg0
rm -f /etc/wireguard/clients/*.pub /etc/wireguard/clients/*.ip
systemctl start wg-quick@wg0
```

View File

@@ -0,0 +1,198 @@
# Step 001: WireGuard + UFW + SSH tylko po WG (Ubuntu 24.04, 1 NIC)
Cel: na świeżym Ubuntu 24.04 na bare metalu ustawić:
- **WireGuard** jako prywatny kanał administracyjny,
- **SSH dostępne tylko przez WireGuard**,
- przygotować grunt pod **Agave RPC dostępne tylko przez WireGuard** (8899/8900 prywatnie),
- zostawić porty klastra Agave na publicznym NIC, żeby node mógł się zsynchronizować.
Kolejność jest krytyczna: najpierw uruchom WireGuard i sprawdź, że działa, dopiero potem ograniczaj SSH/firewall.
## Założenia
- Host ma **1 publiczny interfejs** (np. `enp1s0`).
- WireGuard interfejs to `wg0`.
- Masz dostęp awaryjny do konsoli (IPMI/KVM) na wypadek pomyłki.
## Parametry (dostosuj)
```bash
export PUBLIC_IFACE="enp1s0"
export WG_IFACE="wg0"
export WG_SERVER_IP="10.66.66.1/24"
export WG_SERVER_IP_ONLY="10.66.66.1"
export WG_PORT="51820"
export WG_CLIENT_IP="10.66.66.2/32"
export WG_CLIENT_PUBKEY="<PASTE_CLIENT_PUBLIC_KEY>"
# Agave/validator ports
export RPC_PORT="8899"
export WS_PORT="8900"
export DYNAMIC_PORT_RANGE="8000:8020"
```
## 1) WireGuard (nie ruszaj jeszcze SSH)
### Instalacja
```bash
apt-get update
apt-get install -y wireguard
```
### Klucze serwera
```bash
umask 077
wg genkey | tee /etc/wireguard/server.key >/dev/null
cat /etc/wireguard/server.key | wg pubkey | tee /etc/wireguard/server.pub >/dev/null
cat /etc/wireguard/server.pub
```
### Konfiguracja `/etc/wireguard/wg0.conf`
```bash
cat >/etc/wireguard/${WG_IFACE}.conf <<EOF
[Interface]
Address = ${WG_SERVER_IP}
ListenPort = ${WG_PORT}
PrivateKey = $(cat /etc/wireguard/server.key)
[Peer]
PublicKey = ${WG_CLIENT_PUBKEY}
AllowedIPs = ${WG_CLIENT_IP}
EOF
```
Start:
```bash
systemctl enable --now wg-quick@${WG_IFACE}
wg show
ip a show ${WG_IFACE}
```
Test z klienta (po zestawieniu tunelu): `ping 10.66.66.1`.
## 2) UFW: public tylko WG + porty klastra; prywatnie RPC/SSH po wg0
Instalacja:
```bash
apt-get install -y ufw
```
Public: pozwól na WireGuard:
```bash
ufw allow ${WG_PORT}/udp
```
Public: pozwól na porty klastra Agave (żeby node mógł syncować).
Uwaga: Solana/Agave używa głównie **UDP** w tym zakresie; jeśli będziesz miał problemy z reachability, rozważ dopuszczenie też TCP w tym samym zakresie.
```bash
ufw allow in on "${PUBLIC_IFACE}" to any port ${DYNAMIC_PORT_RANGE}/udp
```
Prywatnie (wg0): RPC/WS:
```bash
ufw allow in on "${WG_IFACE}" to any port ${RPC_PORT} proto tcp
ufw allow in on "${WG_IFACE}" to any port ${WS_PORT} proto tcp
```
Na tym etapie NIE blokuj jeszcze publicznego SSH (żeby się nie zablokować).
Włącz UFW:
```bash
ufw enable
ufw status verbose
```
## 3) SSH tylko po WireGuard (dopiero gdy WG działa)
### 3.1) UFW: pozwól na SSH po wg0
```bash
ufw allow in on "${WG_IFACE}" to any port 22 proto tcp
```
### 3.2) sshd: nasłuch tylko na WG IP
Edytuj `sshd_config`:
```bash
sed -n '1,220p' /etc/ssh/sshd_config
```
Dodaj (lub ustaw) na końcu pliku:
```conf
PasswordAuthentication no
PermitRootLogin no
ListenAddress 10.66.66.1
```
Zanim zrestartujesz SSH:
- miej otwartą obecną sesję SSH,
- zestaw WG na kliencie,
- przygotuj drugi terminal do testu po `10.66.66.1`.
Restart:
```bash
systemctl restart ssh
ss -lntp | grep -E ':22\\b' || true
```
Test z klienta po WG:
```bash
ssh <user>@10.66.66.1
```
Jeśli działa: zablokuj publiczny SSH:
```bash
ufw deny 22/tcp
ufw status verbose
```
## 4) Kernel/sysctl baseline pod Agave (na tym etapie bez uruchamiania Agave)
Minimalne wartości, które już mamy w Ansible (i kilka dodatkowych bezpiecznych):
```bash
cat >/etc/sysctl.d/90-agave.conf <<'EOF'
fs.file-max = 2000000
vm.max_map_count = 1000000
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 250000
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
EOF
sysctl --system
```
Limity `nofile`:
```bash
cat >/etc/security/limits.d/90-solana.conf <<'EOF'
* soft nofile 1000000
* hard nofile 1000000
EOF
```
## 5) Co dalej
- Instalacja i uruchomienie Agave: patrz `doc/etap-007-manual-baremetal-rpc-commands.md`.
- Narzedzia do generowania konfiguracji WireGuard (server/client) + reset wg0: patrz `doc/step-001-wireguard-tools.md`.
- Docelowo: bind RPC/WS do WG IP (zamiast `0.0.0.0`) i spójne, minimalne reguły firewall.

View File

@@ -0,0 +1,261 @@
Step 002: Plan wdrozenia Solana RPC (Agave mainnet, non-voting, private RPC + Geyser->Postgres + monitoring + NOC tmux)
Zalozenia (default):
- WG iface: wg0, WG subnet: 10.66.66.0/24, WG IP baremetala: 10.66.66.1
- Public iface: eth0, public IP: 149.50.116.219
- RPC: 8899 (HTTP), WS: 8900 (WS = RPC_PORT+1)
- Postgres (lokalnie): 5432 (DB: geyser, user: geyser)
- (Optional) Yellowstone gRPC: 10000 (tylko po WG)
- Prometheus: 9090, Grafana: 3000, node_exporter: 9100 (tylko po WG)
- Dynamic port range (cluster): 8000-8020/udp (public eth0)
- User: solana
- Dirs:
- /var/lib/solana (ledger/accounts)
- /etc/solana (configs)
- /var/log/solana (logs)
0) Preconditions (check)
# WG dziala
ip -4 a show wg0
wg show
# SSH po WG dziala (z laptop/VPS):
# ssh user@10.66.66.1
1) Packages
sudo apt-get update
sudo apt-get install -y \
tmux btop \
sysstat iotop \
ethtool nstat \
chrony curl jq git ca-certificates \
ripgrep \
smartmontools nvme-cli \
build-essential pkg-config libssl-dev \
clang llvm-dev libclang-dev \
cmake protobuf-compiler libudev-dev zlib1g-dev
2) Users/Dirs
sudo getent group solana >/dev/null || sudo groupadd --system solana
sudo id -u solana >/dev/null 2>&1 || sudo useradd --system --gid solana \
--home-dir /var/lib/solana --create-home --shell /bin/bash solana
sudo install -d -o root -g root -m 0755 /etc/solana /opt/solana/bin
sudo install -d -o solana -g solana -m 0750 \
/var/lib/solana /var/lib/solana/ledger /var/lib/solana/accounts /var/log/solana
# identity permissions (po utworzeniu pliku):
# sudo chown solana:solana /var/lib/solana/identity.json
# sudo chmod 600 /var/lib/solana/identity.json
2A) Storage (rekomendowane)
# Variant A: jesli /dev/nvme0n1 jest wolny i chcesz go podzielic 50/50:
# doc/step-002a-storage-nvme0n1-xfs.txt
#
# Variant B (mpabi): masz juz 2x NVMe z XFS:
# - SOLACCT na accounts (nvme0n1p1)
# - SOLLEDG na ledger (nvme1n1p1)
# doc/step-002a-storage-existing-solacct-solledg-xfs.txt
3) Firewall/WG binding (private-only)
# UFW: deny everything inbound by default
sudo ufw default deny incoming
sudo ufw default allow outgoing
# WireGuard (public)
sudo ufw allow 51820/udp
# SSH only via WG
sudo ufw allow in on wg0 to any port 22 proto tcp
sudo ufw deny in on eth0 to any port 22 proto tcp
# RPC/WS only via WG
sudo ufw allow in on wg0 to any port 8899 proto tcp
sudo ufw allow in on wg0 to any port 8900 proto tcp
sudo ufw deny in on eth0 to any port 8899 proto tcp
sudo ufw deny in on eth0 to any port 8900 proto tcp
# Yellowstone gRPC only via WG
sudo ufw allow in on wg0 to any port 10000 proto tcp
sudo ufw deny in on eth0 to any port 10000 proto tcp
# Monitoring only via WG
sudo ufw allow in on wg0 to any port 9100 proto tcp
sudo ufw allow in on wg0 to any port 9090 proto tcp
sudo ufw allow in on wg0 to any port 3000 proto tcp
sudo ufw deny in on eth0 to any port 9100 proto tcp
sudo ufw deny in on eth0 to any port 9090 proto tcp
sudo ufw deny in on eth0 to any port 3000 proto tcp
# Cluster ports (public, inbound) for sync (UDP)
sudo ufw allow in on eth0 to any port 8000:8020 proto udp
# (opcjonalnie) jesli bedziesz mial problemy z reachability:
# sudo ufw allow in on eth0 to any port 8000:8020 proto tcp
sudo ufw enable
sudo ufw status verbose
4) Validator (Agave) config + systemd (non-voting RPC-only)
# sysctl baseline (Agave startup checks)
sudo tee /etc/sysctl.d/90-agave.conf >/dev/null <<'EOF'
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
EOF
sudo sysctl --system
# ulimit baseline
sudo tee /etc/security/limits.d/90-solana.conf >/dev/null <<'EOF'
* soft nofile 1000000
* hard nofile 1000000
EOF
# Agave tools (solana-keygen etc.) for solana user
sudo -u solana -H bash -lc 'sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"'
# Copy binaries to /opt/solana/bin (avoid symlink permission gotchas)
sudo cp -a /var/lib/solana/.local/share/solana/install/active_release/bin/. /opt/solana/bin/
sudo find /opt/solana/bin -maxdepth 1 -type f -exec chmod 0755 {} \;
/opt/solana/bin/solana --version || true
/opt/solana/bin/solana-keygen --version
# Build agave-validator (pin tag + toolchain)
# Uwaga: dla nowszych stable release'ow binarka moze nie byc publikowana,
# wiec build ze zrodel jest wymagany.
# doc/step-002f-agave-build-from-source-latest.txt
# Identity (create once)
if [ ! -f /var/lib/solana/identity.json ]; then
sudo -u solana -H /opt/solana/bin/solana-keygen new --no-passphrase -o /var/lib/solana/identity.json
sudo chown solana:solana /var/lib/solana/identity.json
sudo chmod 600 /var/lib/solana/identity.json
fi
# Genesis hash (mainnet)
GENESIS_HASH="$(curl -sS -H 'content-type: application/json' \
--data '{"jsonrpc":"2.0","id":1,"method":"getGenesisHash"}' \
https://api.mainnet-beta.solana.com | jq -r .result)"
echo "$GENESIS_HASH"
# Oczekiwane (mainnet): 5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d
# systemd unit (bind RPC to WG/localhost only)
# UWAGA: w unit file NIE uzywaj `\\` na koncu linii w ExecStart.
# Jesli wpiszesz `\\`, agave-validator dostanie literalny argument `\` i padnie z:
# "error: The subcommand '\\' wasn't recognized"
#
# Najprosciej: ExecStart w 1 linii, bez "kontynuacji" backslashami.
# Ale: zeby uniknac problemow z wklejaniem/lamaniem linii, ponizej jest bezpieczna wersja wielolinijkowa.
sudo tee /etc/systemd/system/agave-validator.service >/dev/null <<'EOF'
[Unit]
Description=Agave validator (mainnet, RPC-only, non-voting)
After=network-online.target wg-quick@wg0.service
Wants=network-online.target wg-quick@wg0.service
[Service]
Type=simple
User=solana
Group=solana
WorkingDirectory=/var/lib/solana
Environment=RUST_LOG=info
LimitNOFILE=1048576
Restart=always
RestartSec=5
TimeoutStopSec=120
ExecStart=/opt/solana/bin/agave-validator \
--identity /var/lib/solana/identity.json \
--no-voting \
--private-rpc \
--ledger /var/lib/solana/ledger \
--accounts /var/lib/solana/accounts \
--log /var/log/solana/validator.log \
--bind-address 0.0.0.0 \
--rpc-bind-address 10.66.66.1 \
--rpc-port 8899 \
--dynamic-port-range 8000-8020 \
--entrypoint entrypoint.mainnet-beta.solana.com:8001 \
--expected-genesis-hash 5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d \
--full-rpc-api \
--limit-ledger-size 50000000
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ReadWritePaths=/var/lib/solana/ledger /var/lib/solana/accounts /var/log/solana /var/lib/solana
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now agave-validator
systemctl is-active agave-validator
journalctl -u agave-validator -n 80 --no-pager
# Jesli padnie na genesis (brak genesis.tar.bz2):
# doc/step-002c-genesis-download-mainnet.txt
#
# Jesli padnie na snapshot (mainnet wymaga snapshot):
# doc/step-002d-snapshot-download-mainnet.txt
#
# Jesli chcesz najpierw odpalic validator z CLI (test), zanim przepniesz na systemd:
# doc/step-002e-agave-validator-run-cli.txt
5) Geyser plugin -> Postgres
# Krok po kroku:
# doc/step-003-geyser-plugin-postgres.txt
#
# W skrocie:
# - budujesz plugin .so i wrzucasz do /opt/solana/plugins/
# - tworzysz /etc/solana/geyser-postgres.json
# - dopisujesz do ExecStart:
# --geyser-plugin-config /etc/solana/geyser-postgres.json
6) Drift DLOB (2 markets) (on VPS, not baremetal) (placeholder)
# VPS env:
# PERP_MARKETS_TO_LOAD=0,75
# USE_GRPC=true
# GRPC_ENDPOINT=http://10.66.66.1:10000
7) Monitoring (bind to WG only) (placeholder)
# node_exporter (prefer bind to WG):
# sudo apt-get install -y prometheus-node-exporter
# sudo systemctl enable --now prometheus-node-exporter
#
# Prometheus/Grafana (bind to 10.66.66.1):
# sudo apt-get install -y prometheus grafana
# sudo systemctl enable --now prometheus grafana-server
8) NOC tmux (btop) (placeholder)
# Create tmux session:
# btop
# iostat -x 1
# vmstat 1
# nstat -az
# ethtool -S eth0 | head -n 40 (wrap in watch)
# journalctl -fu agave-validator
# while true; do curl getHealth/getSlot; sleep 1; done
9) Troubleshooting checklist
# Services:
systemctl status agave-validator --no-pager
journalctl -u agave-validator -n 200 --no-pager
# Sockets and binds:
ss -lntp | egrep ':22\\b|:8899\\b|:8900\\b|:10000\\b|:9100\\b|:9090\\b|:3000\\b' || true
# Firewall:
sudo ufw status verbose
# RPC local:
curl -sS -H 'content-type: application/json' \
--data '{"jsonrpc":"2.0","id":1,"method":"getHealth"}' \
http://127.0.0.1:8899 | jq .
5-min quick verification checklist
wg show
sudo ufw status verbose
systemctl is-active agave-validator
ss -lntp | egrep ':8899\\b|:8900\\b|:10000\\b|:9090\\b|:3000\\b|:9100\\b' || true
curl -sS -H 'content-type: application/json' \
--data '{"jsonrpc":"2.0","id":1,"method":"getSlot"}' \
http://10.66.66.1:8899 | jq .

View File

@@ -0,0 +1,214 @@
Step 002: Instalacja Solana RPC (Agave) na baremetalu (po WireGuard)
Stan startowy:
- Masz dzialajacy WireGuard: z laptopa/VPS pingujesz 10.66.66.1 i logujesz sie: `ssh user@10.66.66.1`
- SSH po publicznym eth0 jest odciete (UFW deny na eth0:22), SSH dziala tylko po wg0.
- RPC ma byc prywatny: porty 8899/8900 tylko po wg0, ale node ma miec publiczne porty klastra (UDP dynamic range) zeby syncowal.
UWAGA o prywatnosci:
- RPC/WS bindujemy na wg0 (10.66.66.1), zeby NIE wystawic RPC na publicznym eth0 nawet gdy UFW jest wylaczone.
- Dodatkowo i tak robimy UFW "deny" na eth0:8899/8900 (defense-in-depth).
Parametry (dostosuj, przyklad mainnet):
- PUBLIC_IFACE=eth0
- WG_IFACE=wg0
- WG_IP=10.66.66.1
- ENDPOINT_RPC_PORT=8899
- ENDPOINT_WS_PORT=8900 (WS = RPC_PORT+1)
- DYNAMIC_PORT_RANGE="8000:8020"
0) Storage (rekomendowane)
Jesli masz osobny NVMe na dane (ledger/accounts), zrob najpierw:
doc/step-002a-storage-nvme0n1-xfs.txt
albo (mpabi, 2x NVMe z juz istniejacym XFS):
doc/step-002a-storage-existing-solacct-solledg-xfs.txt
1) Bazowe pakiety
sudo apt-get update
sudo apt-get install -y \
chrony curl jq git \
ripgrep \
smartmontools nvme-cli \
build-essential pkg-config libssl-dev \
clang llvm-dev libclang-dev \
cmake protobuf-compiler libudev-dev zlib1g-dev
2) Uzytkownik i katalogi
sudo getent group solana >/dev/null || sudo groupadd --system solana
sudo id -u solana >/dev/null 2>&1 || sudo useradd --system --gid solana \
--home-dir /var/lib/solana --create-home --shell /bin/bash solana
sudo install -d -o root -g root -m 0755 /etc/solana /opt/solana/bin
sudo install -d -o solana -g solana -m 0750 \
/var/lib/solana /var/lib/solana/ledger /var/lib/solana/accounts /var/log/solana
# Jesli ledger/accounts sa mountpointami (Step 002A), upewnij sie, ze root FS jest writable dla usera solana:
sudo chown solana:solana /var/lib/solana/ledger /var/lib/solana/accounts
sudo chmod 750 /var/lib/solana/ledger /var/lib/solana/accounts
3) Sysctl + ulimit (baseline pod Agave)
sudo tee /etc/sysctl.d/90-agave.conf >/dev/null <<'EOF'
fs.file-max = 2000000
vm.max_map_count = 1000000
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 250000
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
EOF
sudo sysctl --system
sudo tee /etc/security/limits.d/90-solana.conf >/dev/null <<'EOF'
* soft nofile 1000000
* hard nofile 1000000
EOF
4) Firewall (UFW): RPC prywatnie po wg0, porty klastra publicznie po eth0
sudo ufw default deny incoming
sudo ufw default allow outgoing
# WireGuard z internetu
sudo ufw allow 51820/udp
# SSH tylko po wg0 (zakladamy, ze tak chcesz)
sudo ufw allow in on wg0 to any port 22 proto tcp
sudo ufw deny in on eth0 to any port 22 proto tcp
# RPC/WS tylko po wg0
sudo ufw allow in on wg0 to any port 8899 proto tcp
sudo ufw allow in on wg0 to any port 8900 proto tcp
sudo ufw deny in on eth0 to any port 8899 proto tcp
sudo ufw deny in on eth0 to any port 8900 proto tcp
# Porty klastra (inbound) na eth0 - UDP
sudo ufw allow in on eth0 to any port 8000:8020 proto udp
# (Opcjonalnie) jesli bedziesz mial problemy z reachability:
# sudo ufw allow in on eth0 to any port 8000:8020 proto tcp
sudo ufw enable
sudo ufw status verbose
5) Narzedzia Solana/Anza (dla `solana`): solana-keygen itd.
sudo -u solana -H bash -lc 'sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"'
# Weryfikacja, ze installer utworzyl active_release/bin (bez exit, zeby nie ubijac pane/tmux):
sudo -u solana -H bash -lc 'ls -ld ~/.local/share/solana/install/active_release/bin && ls -1 ~/.local/share/solana/install/active_release/bin | head'
# Kopiuj binarki do /opt/solana/bin (zamiast symlinkow - mniej problemow z uprawnieniami)
sudo cp -a /var/lib/solana/.local/share/solana/install/active_release/bin/. /opt/solana/bin/
sudo find /opt/solana/bin -maxdepth 1 -type f -exec chmod 0755 {} \;
/opt/solana/bin/solana-keygen --version
6) Build `agave-validator` ze zrodel i instalacja do /opt/solana/bin
# Najnowszy stable release dla mainnet wybieraj z GitHub Releases (tag v3.0.x).
# Krok po kroku (w tym automatyczny wybor tagu):
doc/step-002f-agave-build-from-source-latest.txt
7) Identity (tylko raz) + backup poza hostem
if [ ! -f /var/lib/solana/identity.json ]; then
sudo -u solana -H bash -lc \
"/opt/solana/bin/solana-keygen new --no-passphrase -o /var/lib/solana/identity.json"
sudo chmod 0600 /var/lib/solana/identity.json
sudo chown solana:solana /var/lib/solana/identity.json
fi
sudo -u solana -H bash -lc \
"/opt/solana/bin/solana-keygen pubkey /var/lib/solana/identity.json"
8) (Opcjonalnie, rekomendowane) Genesis hash z publicznego RPC
Mainnet:
GENESIS_HASH="$(curl -sS -H 'content-type: application/json' \
--data '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"getGenesisHash\"}' \
https://api.mainnet-beta.solana.com | jq -r .result)"
echo "$GENESIS_HASH"
Testnet:
GENESIS_HASH="$(curl -sS -H 'content-type: application/json' \
--data '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"getGenesisHash\"}' \
https://api.testnet.solana.com | jq -r .result)"
echo "$GENESIS_HASH"
9) Systemd unit: /etc/systemd/system/agave-validator.service
Minimalny unit (RPC node, non-voting). RPC/WS bind na wg0 (10.66.66.1).
Jesli widzisz w logach:
"error: The subcommand '\\' wasn't recognized"
to zrob:
doc/step-002b-agave-validator-systemd.txt
Jesli chcesz najpierw odpalic recznie z CLI (test) przed systemd:
doc/step-002e-agave-validator-run-cli.txt
sudo tee /etc/systemd/system/agave-validator.service >/dev/null <<'EOF'
[Unit]
Description=Agave validator (mainnet, RPC-only, non-voting)
After=network-online.target wg-quick@wg0.service
Wants=network-online.target wg-quick@wg0.service
[Service]
Type=simple
User=solana
Group=solana
WorkingDirectory=/var/lib/solana
Environment=RUST_LOG=info
LimitNOFILE=1048576
Restart=always
RestartSec=5
TimeoutStopSec=120
ExecStart=/opt/solana/bin/agave-validator \
--identity /var/lib/solana/identity.json \
--no-voting \
--private-rpc \
--ledger /var/lib/solana/ledger \
--accounts /var/lib/solana/accounts \
--log /var/log/solana/validator.log \
--bind-address 0.0.0.0 \
--rpc-bind-address 10.66.66.1 \
--rpc-port 8899 \
--dynamic-port-range 8000-8020 \
--entrypoint entrypoint.mainnet-beta.solana.com:8001 \
--expected-genesis-hash 5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d \
--full-rpc-api \
--limit-ledger-size 50000000
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ReadWritePaths=/var/lib/solana/ledger /var/lib/solana/accounts /var/log/solana /var/lib/solana
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now agave-validator
Jesli chcesz dodac genesis hash:
- dopisz w ExecStart linie:
--expected-genesis-hash <GENESIS_HASH>
10) Weryfikacja
systemctl is-active agave-validator
journalctl -u agave-validator -n 200 --no-pager
# z samego hosta (getHealth moze zwracac blad dopoki node nie dogoni klastra):
curl -sS -H 'content-type: application/json' \
--data '{"jsonrpc":"2.0","id":1,"method":"getVersion"}' \
http://10.66.66.1:8899 | jq .
# z laptopa/VPS po WG:
curl -sS -H 'content-type: application/json' \
--data '{"jsonrpc":"2.0","id":1,"method":"getSlot"}' \
http://10.66.66.1:8899 | jq .
Jesli cos nie dziala:
- `sudo wg show` (czy WG jest OK)
- `sudo ufw status verbose` (czy RPC nie jest blokowane po wg0)
- `journalctl -u agave-validator -n 200 --no-pager` (logi Agave)
Typowe fixy:
- "error: The subcommand '\\' wasn't recognized":
doc/step-002b-agave-validator-systemd.txt
- "failed to open genesis" / brak `genesis.tar.bz2`:
doc/step-002c-genesis-download-mainnet.txt
- "did you forget to provide a snapshot" (mainnet bez snapshot nie ruszy):
doc/step-002d-snapshot-download-mainnet.txt

View File

@@ -0,0 +1,51 @@
Step 002A (variant): Storage na 2x NVMe (XFS juz istnieje) -> SOLACCT=accounts, SOLLEDG=ledger
Context (wg hosta mpabi):
- /dev/nvme0n1p1: XFS LABEL=SOLACCT (nowy dysk na accounts)
- /dev/nvme1n1p1: XFS LABEL=SOLLEDG (stary dysk ~63% used na ledger)
Cel:
- NIE formatowac (bez kasowania danych), tylko zamontowac i spiac w /etc/fstab:
- /var/lib/solana/accounts -> SOLACCT
- /var/lib/solana/ledger -> SOLLEDG
0) Check (zanim dotkniesz fstab)
lsblk -f
sudo blkid /dev/nvme0n1p1 /dev/nvme1n1p1
# Upewnij sie, ze NIC nie jest zamontowane w /var/lib/solana/*:
findmnt /var/lib/solana/accounts || true
findmnt /var/lib/solana/ledger || true
1) Mountpointy
sudo getent group solana >/dev/null || sudo groupadd --system solana
sudo id -u solana >/dev/null 2>&1 || sudo useradd --system --gid solana \
--home-dir /var/lib/solana --create-home --shell /bin/bash solana
sudo install -d -o solana -g solana -m 0750 /var/lib/solana/accounts /var/lib/solana/ledger
2) /etc/fstab (po UUID)
UUID_ACCTS="$(sudo blkid -s UUID -o value /dev/nvme0n1p1)"
UUID_LEDGER="$(sudo blkid -s UUID -o value /dev/nvme1n1p1)"
echo "UUID=$UUID_ACCTS /var/lib/solana/accounts xfs noatime,nodiratime 0 2" | sudo tee -a /etc/fstab >/dev/null
echo "UUID=$UUID_LEDGER /var/lib/solana/ledger xfs noatime,nodiratime 0 2" | sudo tee -a /etc/fstab >/dev/null
sudo mount -a
sudo systemctl daemon-reload
3) Permissions (po mount) - krytyczne
sudo chown solana:solana /var/lib/solana/accounts /var/lib/solana/ledger
sudo chmod 750 /var/lib/solana/accounts /var/lib/solana/ledger
4) Weryfikacja
df -hT /var/lib/solana/accounts /var/lib/solana/ledger
findmnt /var/lib/solana/accounts
findmnt /var/lib/solana/ledger
stat -c '%U:%G %a %n' /var/lib/solana/accounts /var/lib/solana/ledger
5) Uwaga (bezpieczenstwo / dane)
- Jesli na SOLLEDG masz stare dane ledgera z innej instalacji, to validator moze startowac na "starym stanie".
Wtedy najlepiej wyczyscic ledger przed pobraniem snapshotow:
doc/step-002d-snapshot-download-mainnet.txt (sekcja czyszczenia)

View File

@@ -0,0 +1,64 @@
Step 002A: Storage pod Solana data (nvme0n1 -> XFS -> /var/lib/solana/{ledger,accounts})
Cel:
- system zostaje na md1 (/)
- dane Solany (ledger/accounts) ida na osobny NVMe: /dev/nvme0n1
UWAGA: kroki nizej KASUJA dane na /dev/nvme0n1.
0) Check (czy nvme0n1 jest do wyczyszczenia)
lsblk /dev/nvme0n1
sudo wipefs -n /dev/nvme0n1
sudo lsblk -f /dev/nvme0n1
1) Partycje (50/50)
sudo apt-get update
sudo apt-get install -y parted
sudo parted -s /dev/nvme0n1 mklabel gpt
sudo parted -s /dev/nvme0n1 mkpart primary 0% 50%
sudo parted -s /dev/nvme0n1 mkpart primary 50% 100%
sudo partprobe /dev/nvme0n1
sudo udevadm settle
lsblk /dev/nvme0n1
ls -l /dev/nvme0n1p1 /dev/nvme0n1p2
2) Format XFS
Uwaga: mkfs.xfs na Ubuntu ma limit label <= 12 znakow.
sudo mkfs.xfs -f -L sol_ledger /dev/nvme0n1p1
sudo mkfs.xfs -f -L sol_accts /dev/nvme0n1p2
sudo blkid /dev/nvme0n1p1 /dev/nvme0n1p2
3) Mountpointy + fstab (po UUID)
sudo install -d -o solana -g solana -m 0750 /var/lib/solana/ledger /var/lib/solana/accounts
UUID_LEDGER="$(sudo blkid -s UUID -o value /dev/nvme0n1p1)"
UUID_ACCTS="$(sudo blkid -s UUID -o value /dev/nvme0n1p2)"
echo "UUID=$UUID_LEDGER /var/lib/solana/ledger xfs noatime,nodiratime 0 2" | sudo tee -a /etc/fstab >/dev/null
echo "UUID=$UUID_ACCTS /var/lib/solana/accounts xfs noatime,nodiratime 0 2" | sudo tee -a /etc/fstab >/dev/null
sudo mount -a
# systemd pokazuje hint po edycji fstab - to jest OK, ale zrob reload:
sudo systemctl daemon-reload
4) WAŻNE: ustaw ownera na zamontowanych filesystemach
Po mount, root filesystemu jest domyslnie root:root 755, wiec validator uruchamiany jako solana nie zapisze ledger/accounts.
sudo chown solana:solana /var/lib/solana/ledger /var/lib/solana/accounts
sudo chmod 750 /var/lib/solana/ledger /var/lib/solana/accounts
5) Weryfikacja
df -hT /var/lib/solana/ledger /var/lib/solana/accounts
findmnt /var/lib/solana/ledger
findmnt /var/lib/solana/accounts
stat -c '%U:%G %a %n' /var/lib/solana/ledger /var/lib/solana/accounts
Dla tego hosta (z wdrozenia):
- /dev/nvme0n1p1 (LABEL=sol_ledger) UUID=56a2c342-3162-43f2-9649-e0dd25db870d
- /dev/nvme0n1p2 (LABEL=sol_accts) UUID=ae5d6625-0910-489a-bbe3-6fd156439e03

View File

@@ -0,0 +1,79 @@
Step 002B: agave-validator pod systemd (RPC-only, non-voting) + fix na problemy z ExecStart
Ten krok naprawia 2 typowe bledy po wklejeniu unit-a:
1) "error: The subcommand '\\' wasn't recognized"
2) RPC nie startuje, bo ExecStart zostal "polamany" (brakuje --rpc-port albo --log ma sciezke do katalogu)
Przyczyna:
- W unit file kontynuacja linii w ExecStart to JEDEN backslash na koncu linii: `\`
- NIE uzywaj `\\` (dwa backslashe) bo wtedy do programu trafia literalny argument `\` i validator pada.
- Dodatkowo: unikaj bardzo dlugich linii, bo clipboard/paste potrafi je rozciac (i systemd wtedy ignoruje reszte).
0) Stop/wycisz restart loop
sudo systemctl disable --now agave-validator
sudo systemctl reset-failed agave-validator
1) Wrzuc poprawny unit (wielolinijkowy ExecStart z poprawna kontynuacja `\`)
sudo tee /etc/systemd/system/agave-validator.service >/dev/null <<'EOF'
[Unit]
Description=Agave validator (mainnet, RPC-only, non-voting)
After=network-online.target wg-quick@wg0.service
Wants=network-online.target wg-quick@wg0.service
[Service]
Type=simple
User=solana
Group=solana
WorkingDirectory=/var/lib/solana
Environment=RUST_LOG=info
LimitNOFILE=1048576
Restart=always
RestartSec=5
TimeoutStopSec=120
ExecStart=/opt/solana/bin/agave-validator \
--identity /var/lib/solana/identity.json \
--no-voting \
--private-rpc \
--ledger /var/lib/solana/ledger \
--accounts /var/lib/solana/accounts \
--log /var/log/solana/validator.log \
--bind-address 0.0.0.0 \
--rpc-bind-address 10.66.66.1 \
--rpc-port 8899 \
--dynamic-port-range 8000-8025 \
--entrypoint entrypoint.mainnet-beta.solana.com:8001 \
--entrypoint entrypoint2.mainnet-beta.solana.com:8001 \
--entrypoint entrypoint3.mainnet-beta.solana.com:8001 \
--expected-genesis-hash 5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d \
--full-rpc-api \
--limit-ledger-size 50000000 \
--geyser-plugin-config /etc/solana/yellowstone-geyser.json
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ReadWritePaths=/var/lib/solana/ledger /var/lib/solana/accounts /var/log/solana /var/lib/solana
[Install]
WantedBy=multi-user.target
EOF
2) Reload + start
sudo systemctl daemon-reload
sudo systemctl enable --now agave-validator
3) Weryfikacja (na hoście)
# Sprawdz, czy systemd widzi kompletne argv[] (ma byc --rpc-port 8899 i poprawna sciezka --log):
systemctl show agave-validator -p ExecStart --no-pager
sudo systemctl status agave-validator --no-pager -l
# Czy porty sluchaja (RPC/WS na wg0):
sudo ss -lntp | egrep ':8899\\b|:8900\\b' || true
# getHealth moze zwracac blad dopoki node nie dogoni klastra, wiec sprawdz getVersion:
curl -sS -H 'content-type: application/json' \
--data '{"jsonrpc":"2.0","id":1,"method":"getVersion"}' \
http://10.66.66.1:8899 | jq .
# Logi:
sudo journalctl -u agave-validator -n 120 --no-pager

View File

@@ -0,0 +1,34 @@
Step 002C: Download genesis (mainnet) do /var/lib/solana/ledger (fix na "failed to open genesis")
Objaw w logach:
Failed to load genesis_config ... missing genesis.bin
Extracting "/var/lib/solana/ledger/genesis.tar.bz2"...
Failed to start validator: failed to open genesis ... No such file or directory
Fix:
- Zaciagnij genesis archive na dysk i zrestartuj serwis.
1) Zatrzymaj validator (zeby nie robil restart loop)
sudo systemctl stop agave-validator
2) Pobierz genesis.tar.bz2 do ledger (jako user `solana`)
sudo -u solana -H bash -lc '
cd /var/lib/solana/ledger
rm -f genesis.tar.bz2 genesis.bin
curl -sSfL https://api.mainnet-beta.solana.com/genesis.tar.bz2 -o genesis.tar.bz2
ls -lh genesis.tar.bz2
'
Oczekiwane:
- plik genesis.tar.bz2 istnieje (ok. kilkadziesiat KB)
3) Start + logi
sudo systemctl start agave-validator
systemctl status agave-validator --no-pager -l
sudo journalctl -u agave-validator -n 80 --no-pager
4) RPC check (local/WG)
curl -sS -H 'content-type: application/json' \
--data '{"jsonrpc":"2.0","id":1,"method":"getVersion"}' \
http://10.66.66.1:8899 | jq .

View File

@@ -0,0 +1,49 @@
Step 002D: Download snapshot (mainnet) do /var/lib/solana/ledger (wymagane na mainnet)
Objaw (bez snapshot):
Failed to start validator: ... did you forget to provide a snapshot
UWAGA:
- Snapshot full jest DUZY (setki GB). Ten krok moze trwac dlugo.
- Pobieramy full + incremental z oficjalnego endpointu (api.mainnet-beta.solana.com).
0) Stop validator
sudo systemctl stop agave-validator
1) (Opcjonalnie, polecane) wyczysc stare dane ledger (zostaw genesis.*)
sudo -u solana -H bash -lc '
cd /var/lib/solana/ledger
rm -rf rocksdb snapshots accounts_index accounts_hash_cache admin.rpc ledger.lock
ls -la | head
'
2) Pobierz FULL + INCREMENTAL snapshot do ledger (jako user `solana`)
sudo -u solana -H bash -lc '
set -euo pipefail
cd /var/lib/solana/ledger
FULL_REL="$(curl -sI https://api.mainnet-beta.solana.com/snapshot.tar.bz2 | awk "/^location:/{print \\$2}" | tr -d "\\r")"
INC_REL="$(curl -sI https://api.mainnet-beta.solana.com/incremental-snapshot.tar.bz2 | awk "/^location:/{print \\$2}" | tr -d "\\r")"
echo "FULL_REL=$FULL_REL"
echo "INC_REL=$INC_REL"
FULL_URL="https://api.mainnet-beta.solana.com${FULL_REL}"
INC_URL="https://api.mainnet-beta.solana.com${INC_REL}"
FULL_FILE="$(basename "$FULL_REL")"
INC_FILE="$(basename "$INC_REL")"
echo "Downloading FULL: $FULL_FILE"
curl -fSL "$FULL_URL" -o "$FULL_FILE"
ls -lh "$FULL_FILE"
echo "Downloading INC: $INC_FILE"
curl -fSL "$INC_URL" -o "$INC_FILE"
ls -lh "$INC_FILE"
'
3) Start validator + logi
sudo systemctl start agave-validator
sudo journalctl -u agave-validator -n 80 --no-pager

View File

@@ -0,0 +1,71 @@
Step 002E: Start agave-validator z linii polecen (test przed systemd)
Cel:
- Odpalic `agave-validator` recznie (w tmux), potwierdzic ze RPC dziala.
- Dopiero potem przepiac na systemd (Step 002B).
Preconditions:
- Masz genesis + snapshoty:
- doc/step-002c-genesis-download-mainnet.txt
- doc/step-002d-snapshot-download-mainnet.txt
- WireGuard dziala (RPC ma byc prywatne po wg0).
0) Upewnij sie, ze systemd nie trzyma procesu na portach
```bash
sudo systemctl stop agave-validator || true
sudo systemctl disable agave-validator || true
```
1) Odpal w tmux (zeby proces nie zginal po rozlaczeniu SSH)
```bash
tmux new -s agave-rpc
```
2) Start (dzialajaca komenda z wdrozenia)
```bash
sudo bash -lc '
set -euo pipefail
ulimit -n 1048576
exec sudo -u solana -H /opt/solana/bin/agave-validator \
--identity /var/lib/solana/identity.json \
--no-voting \
--private-rpc \
--ledger /var/lib/solana/ledger \
--accounts /var/lib/solana/accounts \
--log - \
--bind-address 0.0.0.0 \
--rpc-bind-address 10.66.66.1 \
--rpc-port 8899 \
--dynamic-port-range 8000-8020 \
--entrypoint entrypoint.mainnet-beta.solana.com:8001 \
--entrypoint entrypoint2.mainnet-beta.solana.com:8001 \
--entrypoint entrypoint3.mainnet-beta.solana.com:8001 \
--expected-genesis-hash 5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d \
--full-rpc-api \
--limit-ledger-size 50000000
'
```
3) Weryfikacja (w drugim oknie / innym terminalu)
```bash
ss -lnt | egrep ':8899\\b|:8900\\b' || true
```
```bash
curl -sS -H 'content-type: application/json' \
--data '{"jsonrpc":"2.0","id":1,"method":"getVersion"}' \
http://10.66.66.1:8899 | jq .
```
4) Detach z tmux
- `Ctrl+b d`
5) Stop (gdy chcesz zatrzymac recznie)
- `tmux attach -t agave-rpc`
- `Ctrl+c` (zatrzyma validator)
- `exit`
Next:
- Jak komenda dziala stabilnie, wrzuc unit i odpal przez systemd:
doc/step-002b-agave-validator-systemd.txt

View File

@@ -0,0 +1,70 @@
Step 002F: Build Agave (agave-validator) z najnowszych zrodel (pin do releasu) i instalacja do /opt/solana/bin
Cel:
- Zbudowac `agave-validator` ze zrodel z pinu do konkretnego releasu (stable dla mainnet).
- Nie robic "floating HEAD" (zeby nie wjechal update bez kontroli).
Konwencja:
- "najnowsza wersja" = najnowszy stabilny release dla mainnet z GitHub Releases repo `anza-xyz/agave`.
(Zweryfikuj przed startem: tag typu v3.0.x.)
0) Packages (build deps)
sudo apt-get update
sudo apt-get install -y \
build-essential pkg-config libssl-dev \
clang llvm-dev libclang-dev \
cmake protobuf-compiler libudev-dev zlib1g-dev \
curl jq git ca-certificates
1) Uzytkownik + katalogi
sudo getent group solana >/dev/null || sudo groupadd --system solana
sudo id -u solana >/dev/null 2>&1 || sudo useradd --system --gid solana \
--home-dir /var/lib/solana --create-home --shell /bin/bash solana
sudo install -d -o root -g root -m 0755 /opt/solana/bin
sudo install -d -o solana -g solana -m 0750 /var/lib/solana/src
2) Wybierz tag releasu (stable dla mainnet)
# Opcja A (manual): sprawdz Releases i wpisz tag, np.:
AGAVE_VERSION_TAG="v3.0.13"
# Opcja B (CLI): pobierz liste tagow i wybierz najwyzszy semver (bez rc/beta)
# (Uwaga: to korzysta z network; jesli nie masz neta - uzyj opcji A.)
AGAVE_VERSION_TAG="$(
git ls-remote --refs --tags https://github.com/anza-xyz/agave.git 'v*' \
| awk -F/ '{print $NF}' \
| rg -v '(rc|beta|alpha)' \
| sort -V \
| tail -n1
)"
echo "AGAVE_VERSION_TAG=$AGAVE_VERSION_TAG"
3) Rust toolchain
# Rekomendacja: uzyj rustup (Agave ma pinned toolchain w repo).
sudo -u solana -H bash -lc 'command -v rustup >/dev/null 2>&1 || (curl -sSfL https://sh.rustup.rs | sh -s -- -y)'
4) Clone + build (pin do tagu)
AGAVE_SRC_DIR="/var/lib/solana/src/agave-${AGAVE_VERSION_TAG}"
sudo -u solana -H bash -lc "
set -euo pipefail
export PATH=/var/lib/solana/.cargo/bin:\$PATH
rm -rf \"${AGAVE_SRC_DIR}\"
git clone --depth 1 --branch \"${AGAVE_VERSION_TAG}\" https://github.com/anza-xyz/agave.git \"${AGAVE_SRC_DIR}\"
cd \"${AGAVE_SRC_DIR}\"
# Agave ma wrapper `./cargo`, ktory bierze pinned toolchain z repo.
./cargo build --release -p agave-validator
"
5) Install do /opt/solana/bin
sudo install -o root -g root -m 0755 \
"${AGAVE_SRC_DIR}/target/release/agave-validator" \
/opt/solana/bin/agave-validator
/opt/solana/bin/agave-validator --version
6) Uwagi (operacyjne)
- Jesli budujesz pluginy (Geyser), trzymaj je na tym samym pinned releasie i najlepiej w tej samej "epoce" API.
- Po update do nowego releasu: buduj validator + pluginy ponownie i restartuj systemd.

View File

@@ -0,0 +1,264 @@
Runbook (mpabi): Agave RPC (mainnet, private) + Geyser plugin -> Postgres
Cel:
- Na mpabi odpalic prywatny Agave RPC (non-voting) po WireGuard (wg0).
- Zbudowac Agave ze zrodel (pinned release).
- (Po starcie) dodac Geyser plugin zapisujacy do PostgreSQL.
Skrot (kolejnosc):
1) Preconditions: WG + UFW (SSH tylko po wg0).
2) Storage: SOLACCT->/var/lib/solana/accounts, SOLLEDG->/var/lib/solana/ledger (+ decyzja: wipe czy kontynuacja).
3) Pakiety + sysctl/limits.
4) /opt/solana/bin: Anza tools (solana-keygen itd.).
5) Build Agave ze zrodel (pin tag) + install do /opt/solana/bin/agave-validator.
6) Identity (0600).
7) Genesis + snapshot (mainnet).
8) Systemd: agave-validator (User=solana) + RPC smoke test po WG.
9) Geyser plugin -> Postgres + dopiecie do systemd + weryfikacja.
Konwencja:
- Komendy uruchamiasz jako user: `user` (z sudo).
- Validator dziala jako uzytkownik systemowy: `solana`.
- Biny instalujemy do /opt/solana/bin (root-owned, stabilne sciezki).
- Dane runtime:
- ledger: /var/lib/solana/ledger (SOLLEDG = /dev/nvme1n1p1)
- accounts: /var/lib/solana/accounts (SOLACCT = /dev/nvme0n1p1)
Parametry hosta (mpabi):
- Public iface: enp6s0, public IP: 149.50.116.219
- WireGuard iface: wg0, WG IP: 10.66.66.1
- RPC: 8899, WS: 8900
- Cluster inbound UDP: 8000-8020 (na public)
=====================
0) Preconditions
=====================
1) WireGuard up:
ip -4 a show wg0
wg show
2) SSH powinno byc tylko po wg0 (defense-in-depth):
sudo ufw status verbose
3) Upewnij sie, ze masz sudo do operacji operatorskich (systemctl/journalctl/ufw).
=====================
1) Storage: mount SOLACCT/SOLLEDG
=====================
UWAGA: ten wariant NIE formatuje dyskow (bez kasowania), tylko montuje istniejace XFS.
UUIDs (wg lsblk -f):
- SOLACCT: 055bf4e7-00f0-45a0-a995-2413b14428c4 (/dev/nvme0n1p1)
- SOLLEDG: 6ac7a8c6-efc7-4803-a3dc-a66ea2ef0125 (/dev/nvme1n1p1)
1) (Opcjonalnie) obejrzyj co zajmuje SOLLEDG (zanim podepniesz pod /var/lib/solana/ledger):
sudo mkdir -p /mnt/solledg
sudo mount /dev/nvme1n1p1 /mnt/solledg
sudo du -sh /mnt/solledg/* 2>/dev/null | sort -h | tail
sudo umount /mnt/solledg
2) Utworz usera solana + mountpointy:
sudo getent group solana >/dev/null || sudo groupadd --system solana
sudo id -u solana >/dev/null 2>&1 || sudo useradd --system --gid solana \
--home-dir /var/lib/solana --create-home --shell /bin/bash solana
sudo install -d -o solana -g solana -m 0750 /var/lib/solana/accounts /var/lib/solana/ledger
3) Dodaj wpisy do /etc/fstab (sprawdz czy nie ma juz duplikatow):
grep -nE '(/var/lib/solana/(accounts|ledger))' /etc/fstab || true
UUID_ACCTS="055bf4e7-00f0-45a0-a995-2413b14428c4"
UUID_LEDGER="6ac7a8c6-efc7-4803-a3dc-a66ea2ef0125"
echo "UUID=$UUID_ACCTS /var/lib/solana/accounts xfs noatime,nodiratime 0 2" | sudo tee -a /etc/fstab >/dev/null
echo "UUID=$UUID_LEDGER /var/lib/solana/ledger xfs noatime,nodiratime 0 2" | sudo tee -a /etc/fstab >/dev/null
sudo mount -a
sudo systemctl daemon-reload
4) Permissions (krytyczne):
sudo chown solana:solana /var/lib/solana/accounts /var/lib/solana/ledger
sudo chmod 750 /var/lib/solana/accounts /var/lib/solana/ledger
5) Verify:
df -hT /var/lib/solana/accounts /var/lib/solana/ledger
findmnt /var/lib/solana/accounts
findmnt /var/lib/solana/ledger
stat -c '%U:%G %a %n' /var/lib/solana/accounts /var/lib/solana/ledger
DECYZJA: co robimy z SOLLEDG (63% used)?
- Opcja A: zostawiamy (jesli to stary ledger Solany i chcesz kontynuowac).
- Opcja B: czyscimy i robimy sync od nowa (destrukcyjne, rekomendowane dla powtarzalnosci):
sudo systemctl stop agave-validator 2>/dev/null || true
sudo -u solana -H bash -lc '
cd /var/lib/solana/ledger
rm -rf rocksdb snapshots accounts_index accounts_hash_cache admin.rpc ledger.lock
'
=====================
2) Pakiety + sysctl/limits
=====================
1) Pakiety:
sudo apt-get update
sudo apt-get install -y \
chrony curl jq git ca-certificates ripgrep \
smartmontools nvme-cli \
build-essential pkg-config libssl-dev \
clang llvm-dev libclang-dev \
cmake protobuf-compiler libudev-dev zlib1g-dev
2) Sysctl:
sudo tee /etc/sysctl.d/90-agave.conf >/dev/null <<'EOF'
fs.file-max = 2000000
vm.max_map_count = 1000000
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 250000
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
EOF
sudo sysctl --system
3) ulimit:
sudo tee /etc/security/limits.d/90-solana.conf >/dev/null <<'EOF'
* soft nofile 1000000
* hard nofile 1000000
EOF
=====================
3) Firewall (UFW) dla prywatnego RPC
=====================
Docelowo:
- SSH 22/tcp: tylko wg0
- RPC 8899/tcp, WS 8900/tcp: tylko wg0 (dodatkowo bind na 10.66.66.1)
- WireGuard: 51820/udp (public)
- Cluster sync: 8000-8020/udp (public)
Komendy (jesli jeszcze nie ustawione):
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 51820/udp
sudo ufw allow in on wg0 to any port 22 proto tcp
sudo ufw deny in on enp6s0 to any port 22 proto tcp
sudo ufw allow in on wg0 to any port 8899 proto tcp
sudo ufw allow in on wg0 to any port 8900 proto tcp
sudo ufw deny in on enp6s0 to any port 8899 proto tcp
sudo ufw deny in on enp6s0 to any port 8900 proto tcp
sudo ufw allow in on enp6s0 to any port 8000:8020 proto udp
sudo ufw enable
sudo ufw status verbose
=====================
4) Instalacja narzedzi Solana/Anza do /opt/solana/bin
=====================
1) Installer (jako solana):
sudo install -d -o root -g root -m 0755 /opt/solana/bin
sudo -u solana -H bash -lc 'sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"'
2) Skopiuj biny do /opt/solana/bin:
sudo cp -a /var/lib/solana/.local/share/solana/install/active_release/bin/. /opt/solana/bin/
sudo find /opt/solana/bin -maxdepth 1 -type f -exec chmod 0755 {} \;
3) Verify:
/opt/solana/bin/solana --version || true
/opt/solana/bin/solana-keygen --version
=====================
5) Build + install agave-validator (pinned latest stable)
=====================
Patrz:
- trade-iac/doc/step-002f-agave-build-from-source-latest.txt
Skrót:
1) Wybierz TAG (np. v3.0.x) i zbuduj jako solana do /var/lib/solana/src/
2) Zainstaluj binarke do /opt/solana/bin/agave-validator
3) Verify:
/opt/solana/bin/agave-validator --version
=====================
6) Identity
=====================
1) Utworz raz:
if [ ! -f /var/lib/solana/identity.json ]; then
sudo -u solana -H /opt/solana/bin/solana-keygen new --no-passphrase -o /var/lib/solana/identity.json
sudo chown solana:solana /var/lib/solana/identity.json
sudo chmod 0600 /var/lib/solana/identity.json
fi
2) Zapisz pubkey do notatek:
sudo -u solana -H /opt/solana/bin/solana-keygen pubkey /var/lib/solana/identity.json
=====================
7) Genesis + snapshot (mainnet)
=====================
1) Genesis:
patrz: trade-iac/doc/step-002c-genesis-download-mainnet.txt
2) Snapshot (wymagany na mainnet):
patrz: trade-iac/doc/step-002d-snapshot-download-mainnet.txt
=====================
8) Systemd: agave-validator (RPC-only)
=====================
Unit template:
- trade-iac/doc/step-002b-agave-validator-systemd.txt
Wazne:
- `User=solana`
- `--rpc-bind-address 10.66.66.1`
- `--private-rpc`
- nie uzywaj `\\` w ExecStart (tylko pojedynczy `\` na koncu linii)
Start/verify:
sudo systemctl daemon-reload
sudo systemctl enable --now agave-validator
systemctl is-active agave-validator
sudo journalctl -u agave-validator -n 120 --no-pager
RPC smoke test (po WG):
curl -sS -H 'content-type: application/json' \
--data '{"jsonrpc":"2.0","id":1,"method":"getVersion"}' \
http://10.66.66.1:8899 | jq .
Manual run (exports + komenda w plikach na mpabi):
# Zawiera "solana export" (PATH + SOLANA_RPC_URL) i helper `agave_run()`:
# - /etc/solana/agave-env.sh
# - /etc/solana/agave-validator.args
sudo -u solana -H bash -lc 'source /etc/solana/agave-env.sh; agave_run'
=====================
9) Geyser plugin -> PostgreSQL
=====================
Krok po kroku:
- trade-iac/doc/step-003-geyser-plugin-postgres.txt
Skrót:
1) Postgres:
sudo apt-get install -y postgresql
sudo systemctl enable --now postgresql
2) DB/user:
sudo -u postgres psql -v ON_ERROR_STOP=1 <<'SQL'
DO $$
BEGIN
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'geyser') THEN
CREATE ROLE geyser LOGIN PASSWORD 'CHANGE_ME';
END IF;
END $$;
CREATE DATABASE geyser OWNER geyser;
SQL
3) Build plugin (.so) i instaluj do /opt/solana/plugins/
4) Skopiuj example JSON config do /etc/solana/geyser-postgres.json, ustaw DSN, selektory.
5) Dopisz do unit ExecStart:
--geyser-plugin-config /etc/solana/geyser-postgres.json
i zrestartuj:
sudo systemctl daemon-reload
sudo systemctl restart agave-validator
Verify:
sudo journalctl -u agave-validator -n 200 --no-pager | rg -i 'geyser|plugin|postgres' || true
sudo -u postgres psql -d geyser -c '\\dt' | head

View File

@@ -0,0 +1,122 @@
Step 003: Geyser plugin -> PostgreSQL (Agave) na prywatnym RPC node
Cel:
- Wlaczyc Geyser plugin zapisujacy eventy do Postgresa.
- Na poczatek: dzialajace end-to-end (plugin laduje sie w validatorze + dane wpadaja do DB).
Wazne ryzyka:
- Pisanie wszystkiego do Postgresa na mainnet generuje OGROMNY write load.
Jezeli DB bedzie na tym samym hoście co validator, latwo o IO contention i spadek performance sync.
- Rekomendacja: startuj od selektorow (filtrow), np. tylko programy ktore bot potrzebuje.
0) Preconditions
- Agave dziala pod systemd:
- doc/step-002b-agave-validator-systemd.txt
- Masz pinned Agave release (tag) i go nie zmieniasz "w locie":
- doc/step-002f-agave-build-from-source-latest.txt
1) Postgres (lokalnie, minimalny setup)
sudo apt-get update
sudo apt-get install -y postgresql postgresql-contrib
sudo systemctl enable --now postgresql
# DB + user (dostosuj haslo; nie commituj do repo)
sudo -u postgres psql -v ON_ERROR_STOP=1 <<'SQL'
DO $$
BEGIN
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'geyser') THEN
CREATE ROLE geyser LOGIN PASSWORD 'CHANGE_ME';
END IF;
END $$;
CREATE DATABASE geyser OWNER geyser;
SQL
2) Build plugin ze zrodel (Anza Agave plugin Postgres)
# Repo:
# https://github.com/anza-xyz/agave-geyser-plugin-postgres
#
# Uwaga: plugin i validator musza byc kompatybilne wersjami interfejsu.
# Najbezpieczniej: build pluginu po update Agave i test "plugin loaded".
#
# Opcjonalnie: pin plugin do tagu/commit (np. pod ten sam release co Agave).
# PLUGIN_REF="main" # albo tag/commit
sudo install -d -o root -g root -m 0755 /opt/solana/plugins
sudo install -d -o solana -g solana -m 0750 /var/lib/solana/src
PLUGIN_SRC_DIR="/var/lib/solana/src/agave-geyser-plugin-postgres"
sudo -u solana -H bash -lc "
set -euo pipefail
export PATH=/var/lib/solana/.cargo/bin:\$PATH
rm -rf \"${PLUGIN_SRC_DIR}\"
git clone https://github.com/anza-xyz/agave-geyser-plugin-postgres.git \"${PLUGIN_SRC_DIR}\"
cd \"${PLUGIN_SRC_DIR}\"
if [ \"${PLUGIN_REF:-main}\" != \"main\" ]; then git checkout \"${PLUGIN_REF}\"; fi
cargo build --release
"
# Typowa nazwa .so (sprawdz `ls target/release/*.so`):
sudo cp -a "${PLUGIN_SRC_DIR}"/target/release/*.so /opt/solana/plugins/
sudo chmod 0755 /opt/solana/plugins/*.so
3) Schema w Postgres
# Plugin repo ma SQL do stworzenia schematu. Najpierw znajdz plik:
sudo -u solana -H bash -lc '
cd /var/lib/solana/src/agave-geyser-plugin-postgres
find . -maxdepth 3 -type f -iname \"*schema*.sql\" -o -type f -iname \"create*.sql\" | sort
'
# Potem wykonaj jako postgres na DB geyser (podmien sciezke na znaleziony plik):
# sudo -u postgres psql -d geyser -f /var/lib/solana/src/agave-geyser-plugin-postgres/scripts/create_schema.sql
4) Konfig pluginu (JSON)
# Najbezpieczniej: skopiuj example config z repo pluginu i zmien tylko:
# - sciezke do .so
# - connection string
# - selektory (na poczatek mozesz dac \"*\" tylko na smoke test, potem zawęz)
#
# Znajdz przyklad w repo:
sudo -u solana -H bash -lc '
cd /var/lib/solana/src/agave-geyser-plugin-postgres
find . -maxdepth 4 -type f -iname \"*.json\" | sort
rg -n \"connection_str|libpath|accounts_selector|transaction_selector|selector\" -S . || true
'
sudo install -d -o root -g root -m 0755 /etc/solana
# Start od skopiowania example (podmien sciezke jesli repo ma inna nazwe pliku):
# sudo cp -a /var/lib/solana/src/agave-geyser-plugin-postgres/config.json /etc/solana/geyser-postgres.json
# Uprawnienia: config zawiera haslo, ale validator (User=solana) musi go czytac.
if [ -f /etc/solana/geyser-postgres.json ]; then
sudo chown root:solana /etc/solana/geyser-postgres.json
sudo chmod 0640 /etc/solana/geyser-postgres.json
fi
5) Wlaczenie pluginu w agave-validator (systemd)
# Sprawdz, jak na Twojej wersji nazywa sie flaga:
/opt/solana/bin/agave-validator --help | rg -n \"geyser|plugin\" || true
# Docelowo (w unit file) dopisz:
# --geyser-plugin-config /etc/solana/geyser-postgres.json
#
# Nastepnie:
sudo systemctl daemon-reload
sudo systemctl restart agave-validator
6) Weryfikacja
sudo journalctl -u agave-validator -n 200 --no-pager | rg -i \"geyser|plugin|postgres\" || true
# Czy w DB pojawiaja sie tabele (zalezy od schema.sql):
sudo -u postgres psql -d geyser -c \"\\dt\" | head
# Czy rosną inserty (przyklad - dostosuj nazwy tabel):
# sudo -u postgres psql -d geyser -c 'select count(*) from account;'
7) Performance (minimum sanity)
- Jezeli validator zacznie znacząco odstawać, zacznij od:
- zawężenia selektorow (unikaj \"*\" po smoke tescie)
- zmniejszenia batch/threads
- przeniesienia DB na osobny host (lub przynajmniej na osobny NVMe)

View File

@@ -0,0 +1,13 @@
apiVersion: traefik.io/v1alpha1
kind: IngressRouteTCP
metadata:
name: gitea-ssh
namespace: gitea
spec:
entryPoints:
- gitssh
routes:
- match: HostSNI(`*`)
services:
- name: gitea-ssh
port: 22

View File

@@ -0,0 +1,14 @@
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
name: traefik
namespace: kube-system
spec:
valuesContent: |-
ports:
gitssh:
port: 2222
expose:
default: true
exposedPort: 2222
protocol: TCP

93
scripts/wireguard/wg_client.py Executable file
View File

@@ -0,0 +1,93 @@
#!/usr/bin/env python3
import argparse
import os
import shutil
import subprocess
import sys
from pathlib import Path
from datetime import datetime
WG_DIR = Path("/etc/wireguard")
WG0 = WG_DIR / "wg0.conf"
def require_root():
if os.geteuid() != 0:
print("ERROR: run as root (sudo).", file=sys.stderr)
sys.exit(1)
def run(cmd, check=True, capture=False):
return subprocess.run(
cmd,
check=check,
text=True,
stdout=subprocess.PIPE if capture else None,
stderr=subprocess.PIPE if capture else None,
)
def backup_if_exists(p: Path):
if p.exists():
ts = datetime.now().strftime("%Y-%m-%d_%H%M%S")
b = p.with_name(p.name + f".bak.{ts}")
shutil.copy2(p, b)
def cmd_install(src: str):
srcp = Path(src)
if not srcp.exists():
raise RuntimeError(f"Missing source config: {srcp}")
WG_DIR.mkdir(parents=True, exist_ok=True)
os.chmod(WG_DIR, 0o700)
backup_if_exists(WG0)
shutil.copy2(srcp, WG0)
os.chmod(WG0, 0o600)
os.chown(WG0, 0, 0)
print(f"OK: installed {WG0}")
def cmd_up():
run(["wg-quick", "up", "wg0"], check=True)
def cmd_down():
run(["wg-quick", "down", "wg0"], check=False)
def cmd_status():
run(["wg", "show"], check=False)
run(["ip", "a", "show", "wg0"], check=False)
def main():
require_root()
p = argparse.ArgumentParser(prog="wg_client.py")
sub = p.add_subparsers(dest="cmd", required=True)
i = sub.add_parser("install")
i.add_argument("config", help="Path to clientX.conf from server export")
sub.add_parser("up")
sub.add_parser("down")
sub.add_parser("status")
args = p.parse_args()
try:
if args.cmd == "install":
cmd_install(args.config)
elif args.cmd == "up":
cmd_up()
elif args.cmd == "down":
cmd_down()
elif args.cmd == "status":
cmd_status()
except Exception as e:
print(f"ERROR: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()

419
scripts/wireguard/wg_serverctl.py Executable file
View File

@@ -0,0 +1,419 @@
#!/usr/bin/env python3
import argparse
import os
import re
import shutil
import subprocess
import sys
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import Optional, List
WG_KEY_RE = re.compile(r"^[A-Za-z0-9+/]{43}=$")
@dataclass
class Cfg:
wg_dir: Path = Path("/etc/wireguard")
clients_dir: Path = Path("/etc/wireguard/clients")
server_key: Path = Path("/etc/wireguard/server.key")
server_pub: Path = Path("/etc/wireguard/server.pub")
wg0_conf: Path = Path("/etc/wireguard/wg0.conf")
wg_addr: str = "10.66.66.1/24"
wg_port: int = 51820
keepalive: int = 25
client_net_prefix: str = "10.66.66."
client_ip_start: int = 2
client_ip_end: int = 254
def require_root():
if os.geteuid() != 0:
print("ERROR: run as root (sudo).", file=sys.stderr)
sys.exit(1)
def run(
cmd: List[str],
capture: bool = False,
check: bool = True,
input_text: Optional[str] = None,
) -> subprocess.CompletedProcess:
return subprocess.run(
cmd,
text=True,
input=input_text,
stdout=subprocess.PIPE if capture else None,
stderr=subprocess.PIPE if capture else None,
check=check,
)
def ensure_dirs(cfg: Cfg):
cfg.wg_dir.mkdir(parents=True, exist_ok=True)
cfg.clients_dir.mkdir(parents=True, exist_ok=True)
os.chmod(cfg.wg_dir, 0o700)
os.chmod(cfg.clients_dir, 0o700)
def read_first_nonempty_line(p: Path) -> str:
if not p.exists():
return ""
for line in p.read_text(encoding="utf-8", errors="ignore").splitlines():
line = line.strip().replace("\r", "")
if line:
return line
return ""
def validate_key(k: str, what: str):
if not WG_KEY_RE.match(k):
raise ValueError(f"{what} has invalid WireGuard key format: {k!r}")
def chmod600_root(p: Path):
os.chmod(p, 0o600)
os.chown(p, 0, 0)
def backup_if_exists(p: Path):
if p.exists():
ts = datetime.now().strftime("%Y-%m-%d_%H%M%S")
b = p.with_name(p.name + f".bak.{ts}")
shutil.copy2(p, b)
def gen_priv_key_to(path: Path):
priv = run(["wg", "genkey"], capture=True).stdout.strip()
validate_key(priv, "generated private key")
path.write_text(priv + "\n", encoding="utf-8")
chmod600_root(path)
def pubkey_from_priv(priv: str) -> str:
validate_key(priv, "private key")
pub = run(["wg", "pubkey"], capture=True, input_text=priv + "\n").stdout.strip()
validate_key(pub, "generated public key")
return pub
def detect_public_ip() -> str:
# Works on Ubuntu/Debian in typical setups
out = run(
[
"bash",
"-lc",
"ip route get 1.1.1.1 | awk '{for (i=1;i<=NF;i++) if ($i==\"src\") {print $(i+1); exit}}'",
],
capture=True,
).stdout.strip()
if not out:
raise RuntimeError("Could not detect server public IP.")
return out
def restart_wg(unit: str = "wg-quick@wg0"):
run(["systemctl", "restart", unit], capture=False, check=True)
# optional status (non-fatal)
try:
run(["systemctl", "--no-pager", "-l", "status", unit], capture=False, check=False)
except Exception:
pass
def list_clients(cfg: Cfg) -> List[str]:
pubs = sorted(cfg.clients_dir.glob("*.pub"))
return [p.stem for p in pubs]
def used_octets(cfg: Cfg) -> set:
s = set()
for f in cfg.clients_dir.glob("*.ip"):
v = read_first_nonempty_line(f)
if v.isdigit():
s.add(int(v))
return s
def alloc_octet(cfg: Cfg) -> int:
used = used_octets(cfg)
for i in range(cfg.client_ip_start, cfg.client_ip_end + 1):
if i not in used:
return i
raise RuntimeError("No free IPs left in pool.")
def gen_server(cfg: Cfg, force: bool):
ensure_dirs(cfg)
if not force and (cfg.server_key.exists() or cfg.server_pub.exists()):
raise RuntimeError("Server key already exists. Use --force to overwrite.")
gen_priv_key_to(cfg.server_key)
priv = read_first_nonempty_line(cfg.server_key)
pub = pubkey_from_priv(priv)
cfg.server_pub.write_text(pub + "\n", encoding="utf-8")
chmod600_root(cfg.server_pub)
print("OK: generated server keys:")
print(f" - {cfg.server_key} (PRIVATE)")
print(f" - {cfg.server_pub} (PUBLIC)")
print("Server public key:")
print(pub)
def gen_client(cfg: Cfg, name: str, force: bool):
ensure_dirs(cfg)
keyf = cfg.clients_dir / f"{name}.key"
pubf = cfg.clients_dir / f"{name}.pub"
if not force and (keyf.exists() or pubf.exists()):
raise RuntimeError(f"Client {name} exists. Use --force to overwrite.")
gen_priv_key_to(keyf)
priv = read_first_nonempty_line(keyf)
pub = pubkey_from_priv(priv)
pubf.write_text(pub + "\n", encoding="utf-8")
chmod600_root(pubf)
print("OK: generated client keys:")
print(f" - {keyf} (PRIVATE)")
print(f" - {pubf} (PUBLIC)")
print("Client public key:")
print(pub)
def generate_wg0_conf(cfg: Cfg):
ensure_dirs(cfg)
if not cfg.server_key.exists():
raise RuntimeError("Missing server.key. Run gen-server first.")
server_priv = read_first_nonempty_line(cfg.server_key)
validate_key(server_priv, "server private key")
backup_if_exists(cfg.wg0_conf)
lines: List[str] = []
lines += [
"[Interface]",
f"Address = {cfg.wg_addr}",
f"ListenPort = {cfg.wg_port}",
f"PrivateKey = {server_priv}",
]
for name in sorted(list_clients(cfg)):
pubf = cfg.clients_dir / f"{name}.pub"
ipf = cfg.clients_dir / f"{name}.ip"
if not ipf.exists():
raise RuntimeError(f"Client {name} has no .ip file (run add {name}).")
pub = read_first_nonempty_line(pubf)
validate_key(pub, f"client {name} pubkey")
octet = read_first_nonempty_line(ipf)
if not octet.isdigit():
raise RuntimeError(f"Invalid ip octet in {ipf}: {octet!r}")
o = int(octet)
if o < 2 or o > 254:
raise RuntimeError(f"Invalid ip octet in {ipf}: {o}")
lines += [
"",
"[Peer]",
f"# {name}",
f"PublicKey = {pub}",
f"AllowedIPs = {cfg.client_net_prefix}{o}/32",
f"PersistentKeepalive = {cfg.keepalive}",
]
cfg.wg0_conf.write_text("\n".join(lines) + "\n", encoding="utf-8")
chmod600_root(cfg.wg0_conf)
print(f"OK: generated {cfg.wg0_conf}")
def add_client(cfg: Cfg, name: str, pubkey: Optional[str], ip: Optional[str], no_restart: bool):
ensure_dirs(cfg)
pubf = cfg.clients_dir / f"{name}.pub"
ipf = cfg.clients_dir / f"{name}.ip"
# resolve pubkey
if pubkey:
pubkey = pubkey.strip()
validate_key(pubkey, "provided client public key")
pubf.write_text(pubkey + "\n", encoding="utf-8")
chmod600_root(pubf)
else:
if not pubf.exists():
raise RuntimeError(f"Missing {pubf}. Run gen-client {name} or provide --pubkey.")
pubkey = read_first_nonempty_line(pubf)
validate_key(pubkey, f"client {name} pubkey")
# resolve ip octet
if ip is None:
octet = alloc_octet(cfg)
else:
ip = ip.strip()
if ip.startswith(cfg.client_net_prefix):
ip = ip[len(cfg.client_net_prefix) :]
if not ip.isdigit():
raise RuntimeError("--ip must be an octet (2..254) or full IP like 10.66.66.X")
octet = int(ip)
if octet < 2 or octet > 254:
raise RuntimeError("--ip must be 2..254")
if octet in used_octets(cfg):
raise RuntimeError(f"IP octet {octet} already used")
ipf.write_text(f"{octet}\n", encoding="utf-8")
chmod600_root(ipf)
print(f"OK: added peer {name} -> {cfg.client_net_prefix}{octet}/32")
generate_wg0_conf(cfg)
if not no_restart:
restart_wg()
def del_client(cfg: Cfg, name: str, no_restart: bool):
ensure_dirs(cfg)
pubf = cfg.clients_dir / f"{name}.pub"
ipf = cfg.clients_dir / f"{name}.ip"
# keep .key on purpose
if pubf.exists():
pubf.unlink()
if ipf.exists():
ipf.unlink()
print(f"OK: removed peer {name} (pub/ip). Private key kept if existed.")
generate_wg0_conf(cfg)
if not no_restart:
restart_wg()
def export_client_conf(cfg: Cfg, name: str, endpoint: Optional[str]):
ensure_dirs(cfg)
keyf = cfg.clients_dir / f"{name}.key"
ipf = cfg.clients_dir / f"{name}.ip"
if not keyf.exists():
raise RuntimeError(f"Missing {keyf}. Run gen-client {name} on server or create key on client.")
if not ipf.exists():
raise RuntimeError(f"Missing {ipf}. Run add {name} first.")
if not cfg.server_pub.exists():
raise RuntimeError("Missing server.pub. Run gen-server first.")
client_priv = read_first_nonempty_line(keyf)
validate_key(client_priv, f"client {name} private key")
server_pub = read_first_nonempty_line(cfg.server_pub)
validate_key(server_pub, "server public key")
octet = int(read_first_nonempty_line(ipf))
if endpoint is None:
endpoint = f"{detect_public_ip()}:{cfg.wg_port}"
out = cfg.clients_dir / f"{name}.conf"
content = "\n".join(
[
"[Interface]",
f"Address = {cfg.client_net_prefix}{octet}/32",
f"PrivateKey = {client_priv}",
"",
"[Peer]",
f"PublicKey = {server_pub}",
f"Endpoint = {endpoint}",
# Default: only reach the server's WG IP via tunnel.
"AllowedIPs = 10.66.66.1/32",
f"PersistentKeepalive = {cfg.keepalive}",
"",
]
)
out.write_text(content, encoding="utf-8")
chmod600_root(out)
print(f"OK: wrote {out}")
def cmd_list(cfg: Cfg):
ensure_dirs(cfg)
names = list_clients(cfg)
if not names:
print("(no clients)")
return
for n in names:
ipf = cfg.clients_dir / f"{n}.ip"
octet = read_first_nonempty_line(ipf) if ipf.exists() else "?"
print(f"{n}\t{cfg.client_net_prefix}{octet}/32")
def cmd_show(cfg: Cfg, name: str):
ensure_dirs(cfg)
pubf = cfg.clients_dir / f"{name}.pub"
ipf = cfg.clients_dir / f"{name}.ip"
keyf = cfg.clients_dir / f"{name}.key"
print(f"Name: {name}")
if ipf.exists():
print(f"IP: {cfg.client_net_prefix}{read_first_nonempty_line(ipf)}/32")
else:
print("IP: (not assigned)")
if pubf.exists():
print(f"PUB: {read_first_nonempty_line(pubf)}")
else:
print("PUB: (missing)")
print(f"KEY: {'exists' if keyf.exists() else 'missing'} ({keyf})")
def main():
require_root()
cfg = Cfg()
p = argparse.ArgumentParser(prog="wg_serverctl.py")
sub = p.add_subparsers(dest="cmd", required=True)
s = sub.add_parser("gen-server")
s.add_argument("--force", action="store_true")
g = sub.add_parser("gen-client")
g.add_argument("name")
g.add_argument("--force", action="store_true")
a = sub.add_parser("add")
a.add_argument("name")
a.add_argument("--pubkey", help="Client public key (base64, ends with =)")
a.add_argument("--ip", help="IP octet (2..254) or full 10.66.66.X")
a.add_argument("--no-restart", action="store_true")
d = sub.add_parser("del")
d.add_argument("name")
d.add_argument("--no-restart", action="store_true")
ap = sub.add_parser("apply")
ap.add_argument("--no-restart", action="store_true")
sub.add_parser("list")
sh = sub.add_parser("show")
sh.add_argument("name")
ex = sub.add_parser("export")
ex.add_argument("name")
ex.add_argument("--endpoint", help="Override endpoint ip:port (default: auto-detect public ip)")
args = p.parse_args()
try:
if args.cmd == "gen-server":
gen_server(cfg, args.force)
elif args.cmd == "gen-client":
gen_client(cfg, args.name, args.force)
elif args.cmd == "add":
add_client(cfg, args.name, args.pubkey, args.ip, args.no_restart)
elif args.cmd == "del":
del_client(cfg, args.name, args.no_restart)
elif args.cmd == "apply":
generate_wg0_conf(cfg)
if not args.no_restart:
restart_wg()
elif args.cmd == "list":
cmd_list(cfg)
elif args.cmd == "show":
cmd_show(cfg, args.name)
elif args.cmd == "export":
export_client_conf(cfg, args.name, args.endpoint)
else:
p.print_help()
except Exception as e:
print(f"ERROR: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()