[{"content":"이 글은 Neovim 시리즈의 마지막 글이다.\nNeovim 입문: Vim을 넘어서는 첫걸음 Neovim 중급: 생산성을 높이는 기능들 Neovim 고급: 플러그인과 LSP로 IDE처럼 쓰기 나의 Neovim 설정 전체 공개 ← 현재 글 설정 철학 Lua 기반: VimScript 대신 Lua로 모든 설정을 작성 모듈화: 기능별로 파일을 분리해서 관리 최소주의: 꼭 필요한 플러그인만, 23개로 유지 일관된 키매핑: 커스텀 keyMapper 유틸리티로 통일 디렉토리 구조 ~/.config/nvim/ ├── init.lua # 진입점 (1줄) ├── lazy-lock.json # 플러그인 버전 고정 └── lua/ ├── config/ │ ├── init.lua # config 모듈 진입점 │ ├── globals.lua # 전역 변수 (leader 키 등) │ ├── options.lua # Neovim 옵션 │ └── keymaps.lua # 글로벌 키매핑 ├── plugins/ │ ├── alpha.lua # 시작 화면 │ ├── comment.lua # 주석 토글 │ ├── conform.lua # 코드 포매팅 │ ├── indent-blankline.lua # 들여쓰기 가이드 │ ├── kanagawa.lua # 컬러스킴 │ ├── lsp.lua # LSP 설정 │ ├── lualine.lua # 상태줄 │ ├── neo-tree.lua # 파일 탐색기 │ ├── nvim-autopairs.lua # 자동 괄호 │ ├── nvim-cmp.lua # 자동완성 │ ├── nvim-treesitter.lua # 구문 하이라이팅 │ ├── nvim-ufo.lua # 코드 폴딩 │ ├── render-markdown.lua # 마크다운 렌더링 │ ├── telescope.lua # 퍼지 파인더 │ └── vim-floaterm.lua # 플로팅 터미널 └── utils/ └── keyMapper.lua # 키매핑 헬퍼 핵심은 init.lua가 단 1줄이라는 것이다:\nrequire(\u0026#34;config\u0026#34;) config/init.lua에서 globals, options, keymaps, lazy.nvim 순서로 로드한다.\n핵심 옵션 -- lua/config/options.lua opt = vim.opt -- 2칸 탭 opt.tabstop = 2 opt.shiftwidth = 2 opt.softtabstop = 2 opt.expandtab = true opt.smartindent = true opt.wrap = false -- 검색 opt.incsearch = true opt.ignorecase = true opt.smartcase = true -- 대문자가 포함되면 대소문자 구분 -- 줄 번호 opt.number = true opt.relativenumber = true -- 상대 줄 번호 (이동에 유용) -- 기타 opt.termguicolors = true opt.signcolumn = \u0026#34;yes\u0026#34; opt.scrolloff = 10 -- 커서 위아래 10줄 여유 opt.mouse:append(\u0026#34;a\u0026#34;) -- 마크다운 전용 설정 vim.api.nvim_create_autocmd(\u0026#34;FileType\u0026#34;, { pattern = \u0026#34;markdown\u0026#34;, callback = function() vim.opt_local.wrap = true -- 줄 바꿈 활성화 vim.opt_local.linebreak = true -- 단어 단위로 줄 바꿈 vim.opt_local.conceallevel = 2 -- 문법 마커 숨기기 end, }) relativenumber는 처음에는 어색하지만, 5j, 12k 같은 상대 이동을 할 때 줄 수를 바로 알 수 있어서 매우 편하다.\nkeyMapper 유틸리티 모든 키매핑에 일관되게 noremap과 silent를 적용하기 위해 만든 헬퍼다:\n-- lua/utils/keyMapper.lua local keyMapper = function(from, to, mode, opts) local options = { noremap = true, silent = true } mode = mode or \u0026#34;n\u0026#34; if opts then options = vim.tbl_extend(\u0026#34;force\u0026#34;, options, opts) end vim.keymap.set(mode, from, to, options) end return { mapKey = keyMapper } 사용법:\nlocal mapKey = require(\u0026#34;utils.keyMapper\u0026#34;).mapKey mapKey(\u0026#34;\u0026lt;leader\u0026gt;e\u0026#34;, \u0026#34;:Neotree toggle\u0026lt;cr\u0026gt;\u0026#34;) -- Normal 모드 (기본) mapKey(\u0026#34;\u0026lt;\u0026#34;, \u0026#34;\u0026lt;gv\u0026#34;, \u0026#34;v\u0026#34;) -- Visual 모드 지정 전체 키매핑 글로벌 키매핑 키 동작 모드 Space Leader 키 - \u0026lt;leader\u0026gt;e Neo-tree 파일 탐색기 토글 N \u0026lt;leader\u0026gt;h 검색 하이라이트 제거 N Ctrl-h/j/k/l 분할 창 이동 N \u0026lt; / \u0026gt; 들여쓰기 유지하며 인덴트 V Ctrl-; 플로팅 터미널 토글 N Telescope 키매핑 키 동작 \u0026lt;leader\u0026gt;ff 파일 이름 검색 \u0026lt;leader\u0026gt;fg 파일 내용 검색 (grep) \u0026lt;leader\u0026gt;fb 버퍼 목록 \u0026lt;leader\u0026gt;fh 도움말 검색 LSP 키매핑 키 동작 K 호버 문서 gd 정의로 이동 \u0026lt;leader\u0026gt;ca 코드 액션 플러그인 전체 목록 (23개) 핵심 플러그인 역할 lazy.nvim 플러그인 매니저 telescope.nvim 퍼지 파인더 (파일/텍스트/버퍼 검색) neo-tree.nvim 사이드바 파일 탐색기 nvim-cmp 자동완성 엔진 nvim-lspconfig LSP 클라이언트 설정 mason.nvim 언어 서버/포매터 설치 관리 nvim-treesitter 구문 파싱 \u0026amp; 하이라이팅 conform.nvim 저장 시 자동 포매팅 자동완성 소스 플러그인 소스 cmp-nvim-lsp LSP 자동완성 cmp-buffer 버퍼 텍스트 cmp-path 파일 경로 cmp_luasnip 스니펫 LuaSnip 스니펫 엔진 friendly-snippets VS Code 스니펫 모음 UI \u0026amp; 외관 플러그인 역할 kanagawa.nvim 컬러스킴 (dragon 테마) lualine.nvim 하단 상태줄 alpha-nvim 시작 화면 대시보드 nvim-web-devicons 파일 아이콘 indent-blankline.nvim 들여쓰기 시각 가이드 편집 보조 플러그인 역할 Comment.nvim gcc로 주석 토글 nvim-autopairs 괄호/따옴표 자동 닫기 nvim-ufo LSP 기반 코드 폴딩 vim-floaterm 플로팅 터미널 render-markdown.nvim 마크다운 실시간 렌더링 테마: Kanagawa Dragon Kanagawa의 Dragon 변형을 사용한다. 일본 전통 색상에서 영감을 받은 다크 테마로, 눈의 피로가 적다.\n커스터마이징 포인트:\noverrides = function(colors) local theme = colors.theme return { -- 플로팅 윈도우 배경 투명화 NormalFloat = { bg = \u0026#34;none\u0026#34; }, FloatBorder = { bg = \u0026#34;none\u0026#34; }, FloatTitle = { bg = \u0026#34;none\u0026#34; }, -- Telescope UI 커스터마이징 TelescopePromptNormal = { bg = theme.ui.bg_p1 }, TelescopeResultsNormal = { fg = theme.ui.fg_dim, bg = theme.ui.bg_m1 }, TelescopePreviewNormal = { bg = theme.ui.bg_dim }, -- 자동완성 팝업 Pmenu = { fg = theme.ui.shade0, bg = theme.ui.bg_p1 }, PmenuSel = { fg = \u0026#34;NONE\u0026#34;, bg = theme.ui.bg_p2 }, } end, theme = \u0026#34;dragon\u0026#34;, 상태줄(lualine)은 Gruvbox 테마를 사용해서 본문과 미묘하게 다른 톤을 준다.\n마크다운 작성 환경 블로그를 Neovim으로 작성하기 때문에 마크다운 환경을 신경 썼다:\nrender-markdown.nvim: 헤딩, 코드 블록, 테이블, 체크박스를 시각적으로 렌더링 줄 바꿈 활성화: 마크다운 파일에서만 wrap = true conceallevel 2: **bold** 같은 마커를 숨기고 bold 형태로 표시 LSP 구성 Mason으로 3개 언어 서버를 관리한다:\n서버 언어 포매터 lua_ls Lua stylua ts_ls TypeScript/JavaScript prettierd gopls Go gofmt (내장) 새 언어를 추가하려면:\n:Mason에서 언어 서버 설치 lsp.lua의 ensure_installed에 추가 conform.lua에 포매터 추가 (필요 시) 정리 이 설정은 계속 발전 중이다. Neovim의 장점은 내 워크플로우에 맞게 모든 것을 조정할 수 있다는 점이다. 처음에는 남의 설정을 복사하더라도, 하나씩 이해하면서 자기 것으로 만들어가는 과정이 중요하다.\n가장 좋은 Neovim 설정은 내가 이해하고 있는 설정이다.\n","permalink":"https://dbalog.dev/posts/neovim-my-setup/","summary":"23개 플러그인, Lua 모듈 구조, 커스텀 키매핑, 테마 커스터마이징까지. 내가 실제로 사용하는 Neovim 설정을 전부 공개한다.","title":"나의 Neovim 설정 전체 공개"},{"content":"이 글은 Neovim 시리즈의 세 번째 글이다.\nNeovim 입문: Vim을 넘어서는 첫걸음 Neovim 중급: 생산성을 높이는 기능들 Neovim 고급: 플러그인과 LSP로 IDE처럼 쓰기 ← 현재 글 나의 Neovim 설정 전체 공개 플러그인 매니저: lazy.nvim Neovim 플러그인 매니저 중 가장 인기 있는 것이 lazy.nvim이다. 지연 로딩(lazy loading)을 기본으로 지원해서 시작 속도가 빠르다.\n설치 (부트스트랩) ~/.config/nvim/init.lua에 아래를 추가하면 lazy.nvim이 없을 때 자동으로 설치된다:\nlocal lazypath = vim.fn.stdpath(\u0026#34;data\u0026#34;) .. \u0026#34;/lazy/lazy.nvim\u0026#34; if not vim.loop.fs_stat(lazypath) then vim.fn.system({ \u0026#34;git\u0026#34;, \u0026#34;clone\u0026#34;, \u0026#34;--filter=blob:none\u0026#34;, \u0026#34;https://github.com/folke/lazy.nvim.git\u0026#34;, \u0026#34;--branch=stable\u0026#34;, lazypath, }) end vim.opt.rtp:prepend(lazypath) require(\u0026#34;lazy\u0026#34;).setup(\u0026#34;plugins\u0026#34;) 마지막 줄의 \u0026quot;plugins\u0026quot;는 ~/.config/nvim/lua/plugins/ 디렉토리를 의미한다. 이 디렉토리에 파일을 추가하면 자동으로 플러그인이 로드된다.\n플러그인 추가 방법 lua/plugins/ 안에 파일을 만들고 테이블을 반환하면 된다:\n-- lua/plugins/example.lua return { \u0026#34;작성자/플러그인이름\u0026#34;, config = function() require(\u0026#34;플러그인\u0026#34;).setup({ -- 옵션 }) end } 플러그인 관리: :Lazy\nLSP (Language Server Protocol) LSP는 에디터와 언어 서버 사이의 표준 프로토콜이다. 코드 자동완성, 정의로 이동, 에러 표시, 리팩토링 등 IDE 기능의 핵심이다.\n구조 Neovim ←→ nvim-lspconfig ←→ Language Server (lua_ls, gopls, ts_ls, ...) Mason으로 언어 서버 설치 Mason은 언어 서버, 포매터, 린터를 Neovim 안에서 설치/관리하는 도구다.\n-- lua/plugins/lsp.lua return { { \u0026#34;williamboman/mason.nvim\u0026#34;, config = function() require(\u0026#34;mason\u0026#34;).setup() end }, { \u0026#34;williamboman/mason-lspconfig.nvim\u0026#34;, config = function() require(\u0026#34;mason-lspconfig\u0026#34;).setup({ ensure_installed = { \u0026#34;lua_ls\u0026#34;, \u0026#34;ts_ls\u0026#34;, \u0026#34;gopls\u0026#34; } }) end }, { \u0026#34;neovim/nvim-lspconfig\u0026#34;, config = function() vim.lsp.enable({ \u0026#34;lua_ls\u0026#34;, \u0026#34;ts_ls\u0026#34;, \u0026#34;gopls\u0026#34; }) -- LSP 키매핑 vim.keymap.set(\u0026#34;n\u0026#34;, \u0026#34;K\u0026#34;, vim.lsp.buf.hover) vim.keymap.set(\u0026#34;n\u0026#34;, \u0026#34;gd\u0026#34;, vim.lsp.buf.definition) vim.keymap.set(\u0026#34;n\u0026#34;, \u0026#34;\u0026lt;leader\u0026gt;ca\u0026#34;, vim.lsp.buf.code_action) end } } 키 동작 K 커서 위 심볼의 문서 표시 gd 정의로 이동 \u0026lt;leader\u0026gt;ca 코드 액션 (자동 수정 등) Mason UI: :Mason (설치된 서버 확인/추가/삭제)\n자동완성: nvim-cmp nvim-cmp는 Neovim의 자동완성 엔진이다. 여러 소스(LSP, 버퍼, 파일 경로, 스니펫)에서 후보를 가져온다.\n-- lua/plugins/nvim-cmp.lua return { \u0026#34;hrsh7th/nvim-cmp\u0026#34;, dependencies = { \u0026#34;hrsh7th/cmp-nvim-lsp\u0026#34;, -- LSP 소스 \u0026#34;hrsh7th/cmp-buffer\u0026#34;, -- 버퍼 텍스트 소스 \u0026#34;hrsh7th/cmp-path\u0026#34;, -- 파일 경로 소스 \u0026#34;L3MON4D3/LuaSnip\u0026#34;, -- 스니펫 엔진 \u0026#34;saadparwaiz1/cmp_luasnip\u0026#34;, -- 스니펫 소스 \u0026#34;rafamadriz/friendly-snippets\u0026#34;, -- 스니펫 모음 }, config = function() local cmp = require(\u0026#34;cmp\u0026#34;) local luasnip = require(\u0026#34;luasnip\u0026#34;) require(\u0026#34;luasnip.loaders.from_vscode\u0026#34;).lazy_load() cmp.setup({ snippet = { expand = function(args) luasnip.lsp_expand(args.body) end, }, mapping = cmp.mapping.preset.insert({ [\u0026#34;\u0026lt;C-Space\u0026gt;\u0026#34;] = cmp.mapping.complete(), [\u0026#34;\u0026lt;CR\u0026gt;\u0026#34;] = cmp.mapping.confirm({ select = true }), [\u0026#34;\u0026lt;Tab\u0026gt;\u0026#34;] = cmp.mapping.select_next_item(), [\u0026#34;\u0026lt;S-Tab\u0026gt;\u0026#34;] = cmp.mapping.select_prev_item(), }), sources = cmp.config.sources({ { name = \u0026#34;nvim_lsp\u0026#34; }, { name = \u0026#34;luasnip\u0026#34; }, { name = \u0026#34;buffer\u0026#34; }, { name = \u0026#34;path\u0026#34; }, }), }) end } 자동완성 키:\n키 동작 Ctrl-Space 수동으로 완성 목록 열기 Tab 다음 후보 Shift-Tab 이전 후보 Enter 선택 확정 Treesitter: 구문 하이라이팅 Treesitter는 코드를 파싱해서 정확한 구문 하이라이팅을 제공한다. 정규식 기반의 기존 하이라이팅보다 훨씬 정확하다.\n-- lua/plugins/nvim-treesitter.lua return { \u0026#34;nvim-treesitter/nvim-treesitter\u0026#34;, build = \u0026#34;:TSUpdate\u0026#34;, config = function() require(\u0026#34;nvim-treesitter.configs\u0026#34;).setup({ ensure_installed = { \u0026#34;lua\u0026#34;, \u0026#34;go\u0026#34;, \u0026#34;javascript\u0026#34;, \u0026#34;html\u0026#34;, \u0026#34;markdown\u0026#34;, \u0026#34;markdown_inline\u0026#34;, }, highlight = { enable = true }, indent = { enable = true }, }) end } 새 언어 추가: :TSInstall python\nTelescope: 퍼지 파인더 Telescope는 파일, 텍스트, 버퍼, Git 등 모든 것을 검색할 수 있는 퍼지 파인더다.\n-- lua/plugins/telescope.lua return { \u0026#34;nvim-telescope/telescope.nvim\u0026#34;, tag = \u0026#34;0.1.5\u0026#34;, dependencies = { \u0026#34;nvim-lua/plenary.nvim\u0026#34; }, config = function() local builtin = require(\u0026#34;telescope.builtin\u0026#34;) vim.keymap.set(\u0026#34;n\u0026#34;, \u0026#34;\u0026lt;leader\u0026gt;ff\u0026#34;, builtin.find_files) vim.keymap.set(\u0026#34;n\u0026#34;, \u0026#34;\u0026lt;leader\u0026gt;fg\u0026#34;, builtin.live_grep) vim.keymap.set(\u0026#34;n\u0026#34;, \u0026#34;\u0026lt;leader\u0026gt;fb\u0026#34;, builtin.buffers) vim.keymap.set(\u0026#34;n\u0026#34;, \u0026#34;\u0026lt;leader\u0026gt;fh\u0026#34;, builtin.help_tags) end } 키 동작 \u0026lt;leader\u0026gt;ff 파일 이름 검색 \u0026lt;leader\u0026gt;fg 파일 내용 검색 (grep) \u0026lt;leader\u0026gt;fb 열린 버퍼 목록 \u0026lt;leader\u0026gt;fh 도움말 검색 live_grep를 사용하려면 ripgrep이 설치되어 있어야 한다: brew install ripgrep\n파일 탐색기: Neo-tree -- lua/plugins/neo-tree.lua return { \u0026#34;nvim-neo-tree/neo-tree.nvim\u0026#34;, branch = \u0026#34;v3.x\u0026#34;, dependencies = { \u0026#34;nvim-lua/plenary.nvim\u0026#34;, \u0026#34;nvim-tree/nvim-web-devicons\u0026#34;, \u0026#34;MunifTanjim/nui.nvim\u0026#34;, }, } \u0026lt;leader\u0026gt;e로 사이드바 파일 탐색기를 토글할 수 있다. VS Code의 Explorer와 비슷한 역할이다.\n코드 포매팅: conform.nvim -- lua/plugins/conform.lua return { \u0026#34;stevearc/conform.nvim\u0026#34;, config = function() require(\u0026#34;conform\u0026#34;).setup({ formatters_by_ft = { lua = { \u0026#34;stylua\u0026#34; }, javascript = { \u0026#34;prettierd\u0026#34;, \u0026#34;prettier\u0026#34;, stop_after_first = true }, typescript = { \u0026#34;prettierd\u0026#34;, \u0026#34;prettier\u0026#34;, stop_after_first = true }, }, format_on_save = { timeout_ms = 500, lsp_format = \u0026#34;fallback\u0026#34;, }, }) end } 파일 저장 시 자동으로 포매팅된다. 포매터는 Mason으로 설치할 수 있다: :Mason → stylua, prettierd 검색.\n추천 플러그인 조합 최소한의 IDE 환경을 위한 추천 조합:\n카테고리 플러그인 역할 플러그인 관리 lazy.nvim 플러그인 매니저 LSP mason + lspconfig 언어 서버 자동완성 nvim-cmp 코드 완성 구문 treesitter 하이라이팅 검색 telescope 퍼지 파인더 파일 탐색 neo-tree 파일 트리 포매팅 conform.nvim 자동 포매팅 테마 kanagawa / tokyonight / catppuccin 컬러스킴 다음 글 이 플러그인들을 실제로 어떻게 조합하고 커스터마이징하는지, 나의 전체 Neovim 설정을 공개한다.\n→ 나의 Neovim 설정 전체 공개\n","permalink":"https://dbalog.dev/posts/neovim-advanced/","summary":"lazy.nvim으로 플러그인을 관리하고, LSP/자동완성/Treesitter/Telescope를 설정해서 Neovim을 IDE 수준으로 만드는 과정.","title":"Neovim 고급: 플러그인과 LSP로 IDE처럼 쓰기"},{"content":"문제 상황 클라우드 환경에서 흔한 구성이 있다. 보안을 위해 내부 서버(DB, 앱 서버 등)는 외부에서 직접 접속할 수 없고, Bastion 서버(점프 서버)를 통해서만 접근할 수 있는 구조다.\n[내 PC] → [Bastion 서버] → [내부 서버] (공인 IP) (사설 IP) 이런 구조에서 내부 서버에 접속하려면 보통 두 단계를 거친다:\n# 1단계: Bastion 서버에 접속 ssh user@bastion-host # 2단계: Bastion에서 내부 서버에 접속 ssh user@internal-host 매번 두 번 SSH를 치는 건 번거롭고, 파일 전송(scp)은 더 복잡해진다. SSH ProxyJump를 사용하면 이 과정을 한 줄로 줄일 수 있다.\nProxyJump란? OpenSSH 7.3(2016년)부터 추가된 기능으로, SSH 접속 시 중간 서버를 점프 호스트로 지정할 수 있다. 중간 서버에 셸을 열지 않고, TCP 포워딩만 수행한다.\n핵심 포인트:\nBastion 서버에 로그인하지 않고 통과만 한다 최종 목적지까지 암호화가 유지된다 (Bastion에서 트래픽을 볼 수 없다) 파일 전송(scp, rsync)도 동일하게 동작한다 기본 사용법: -J 플래그 ssh -J user@bastion-host user@internal-host 이 한 줄로 Bastion을 거쳐 내부 서버에 바로 접속한다.\n예시 # Bastion(203.0.113.10)을 거쳐 내부 DB 서버(10.0.1.50)에 접속 ssh -J admin@203.0.113.10 dbadmin@10.0.1.50 포트가 다른 경우 # Bastion이 2222 포트를 사용하는 경우 ssh -J admin@203.0.113.10:2222 dbadmin@10.0.1.50 SSH Config로 영구 설정 매번 -J 플래그를 타이핑하는 건 여전히 번거롭다. ~/.ssh/config에 설정해두면 ssh internal-db만으로 접속할 수 있다.\n# Bastion 서버 Host bastion HostName 203.0.113.10 User admin IdentityFile ~/.ssh/id_ed25519 # 내부 DB 서버 (Bastion 경유) Host internal-db HostName 10.0.1.50 User dbadmin ProxyJump bastion IdentityFile ~/.ssh/id_ed25519 이제 이렇게 접속한다:\nssh internal-db 파일 전송도 자연스럽게 동작한다:\n# 내부 서버로 파일 복사 scp backup.sql internal-db:/tmp/ # 내부 서버에서 파일 가져오기 scp internal-db:/var/log/app.log ./ # rsync도 동일 rsync -avz ./deploy/ internal-db:/var/www/app/ 다중 점프 (Multi-hop) Bastion이 여러 단계인 경우도 지원한다.\n[내 PC] → [Bastion 1] → [Bastion 2] → [최종 서버] -J 플래그 방식 ssh -J user@bastion1,user@bastion2 user@final-host 쉼표로 구분하면 순서대로 점프한다.\nSSH Config 방식 Host bastion1 HostName 203.0.113.10 User admin Host bastion2 HostName 10.0.1.1 User admin ProxyJump bastion1 Host final-server HostName 10.0.2.50 User deploy ProxyJump bastion2 체인처럼 연결된다: final-server → bastion2 → bastion1 → 인터넷.\nProxyJump vs ProxyCommand SSH Config에서 비슷한 역할을 하는 ProxyCommand가 있다. 차이점:\n# ProxyJump (간단) Host internal ProxyJump bastion # ProxyCommand (구버전 호환) Host internal ProxyCommand ssh -W %h:%p bastion ProxyJump ProxyCommand 도입 OpenSSH 7.3+ OpenSSH 5.4+ 문법 간단 복잡 다중 점프 쉼표로 구분 중첩 필요 커맨드라인 -J 플래그 -o ProxyCommand=... OpenSSH 7.3 이상이면 ProxyJump를 쓰는 게 낫다. 더 간단하고 다중 점프도 깔끔하다.\nOpenSSH 버전 확인: ssh -V\n실전 팁 1. Bastion 서버에 에이전트 포워딩 내부 서버 접속 시 로컬의 SSH 키를 사용하려면 에이전트 포워딩을 활성화한다:\nHost bastion HostName 203.0.113.10 User admin ForwardAgent yes 보안 주의: ForwardAgent는 Bastion 서버의 root가 에이전트 소켓에 접근할 수 있으므로, 신뢰할 수 있는 서버에서만 사용해야 한다.\n2. 접속 끊김 방지 (KeepAlive) 점프 호스트를 거치면 타임아웃이 발생하기 쉽다:\nHost * ServerAliveInterval 60 ServerAliveCountMax 3 60초마다 keepalive 패킷을 보내서 연결을 유지한다.\n3. 포트 포워딩과 함께 사용 Bastion을 거쳐 내부 DB 포트를 로컬로 포워딩할 수도 있다:\n# 내부 PostgreSQL(5432)을 로컬 15432로 포워딩 ssh -J bastion -L 15432:10.0.1.50:5432 admin@10.0.1.50 -N 이제 localhost:15432로 내부 DB에 접속할 수 있다:\npsql -h localhost -p 15432 -U postgres SSH Config로 설정하면 더 깔끔하다:\nHost db-tunnel HostName 10.0.1.50 User dbadmin ProxyJump bastion LocalForward 15432 localhost:5432 ssh -N db-tunnel # 다른 터미널에서: psql -h localhost -p 15432 -U postgres 정리 상황 명령어 단일 점프 ssh -J bastion internal 다중 점프 ssh -J bastion1,bastion2 final 파일 전송 scp -J bastion file internal:/path 포트 포워딩 ssh -J bastion -L 15432:db:5432 db-host -N SSH ProxyJump는 Bastion 서버 환경에서 필수적인 기능이다. SSH Config에 한 번 설정해두면, 내부 서버도 직접 연결된 것처럼 쓸 수 있다.\n","permalink":"https://dbalog.dev/posts/ssh-proxyjump/","summary":"Bastion(점프) 서버를 거쳐 내부 서버에 접속해야 할 때, SSH ProxyJump를 사용하면 한 줄로 끝난다.","title":"SSH ProxyJump로 내부 서버에 한 번에 접속하기"},{"content":"이 글은 Neovim 시리즈의 두 번째 글이다.\nNeovim 입문: Vim을 넘어서는 첫걸음 Neovim 중급: 생산성을 높이는 기능들 ← 현재 글 Neovim 고급: 플러그인과 LSP로 IDE처럼 쓰기 나의 Neovim 설정 전체 공개 텍스트 오브젝트 Vim의 진짜 힘은 동사 + 대상 조합이다. 텍스트 오브젝트는 \u0026ldquo;대상\u0026quot;에 해당한다.\n문법 {동사}{범위}{대상} 동사: d(삭제), c(변경), y(복사), v(선택) 범위: i(inner, 안쪽), a(around, 바깥 포함) 대상: w(단어), \u0026quot;, ', ), }, ], t(태그) 등 실전 예시 커서가 단어 중간에 있을 때:\n명령 동작 ciw 단어를 지우고 입력 모드 (Change Inner Word) diw 단어 삭제 yiw 단어 복사 따옴표 안에 커서가 있을 때:\n명령 동작 ci\u0026quot; 따옴표 안의 내용을 지우고 입력 di\u0026quot; 따옴표 안의 내용 삭제 da\u0026quot; 따옴표까지 포함해서 삭제 괄호 안에 커서가 있을 때:\n명령 동작 ci( 괄호 안 내용을 변경 da{ 중괄호를 포함해서 삭제 vi[ 대괄호 안 내용을 선택 i vs a의 차이 print(\u0026#34;hello world\u0026#34;) 커서가 hello 위에 있을 때:\ndi\u0026quot; → print(\u0026quot;\u0026quot;) (따옴표 안 내용만 삭제) da\u0026quot; → print() (따옴표까지 삭제) 텍스트 오브젝트를 익히면 편집 속도가 비약적으로 올라간다.\n버퍼, 윈도우, 탭 버퍼 파일을 열면 버퍼에 로드된다. 여러 파일을 동시에 열어둘 수 있다.\n:e file1.lua 파일 열기 (새 버퍼) :ls 열린 버퍼 목록 :bn 다음 버퍼 :bp 이전 버퍼 :bd 버퍼 닫기 :b {숫자} 해당 번호 버퍼로 이동 윈도우 (분할) 화면을 분할해서 여러 버퍼를 동시에 볼 수 있다.\n:split 가로 분할 (또는 Ctrl-w s) :vsplit 세로 분할 (또는 Ctrl-w v) Ctrl-w h/j/k/l 분할 창 간 이동 Ctrl-w = 분할 창 크기 균등화 :close 현재 창 닫기 탭 여러 윈도우 레이아웃을 탭으로 관리할 수 있다.\n:tabnew 새 탭 :tabn 다음 탭 (또는 gt) :tabp 이전 탭 (또는 gT) :tabclose 탭 닫기 실무 팁: 버퍼 + 윈도우 분할을 주로 쓰고, 탭은 맥락이 완전히 다른 작업을 분리할 때 쓰면 좋다.\n매크로 반복 작업을 녹화해서 재생하는 기능이다.\n기본 사용법 q{레지스터} — 녹화 시작 (예: qa) 원하는 작업 수행 q — 녹화 중지 @{레지스터} — 재생 (예: @a) @@ — 마지막 매크로 재생 {숫자}@a — N번 반복 재생 실전 예시 모든 줄 끝에 세미콜론을 추가하고 싶다면:\nqa q 레지스터에 녹화 시작 A; 줄 끝에 ; 추가 Esc Normal 모드로 j 다음 줄로 q 녹화 중지 99@a 99번 반복 레지스터 Vim의 복사/붙여넣기는 레지스터라는 저장소를 사용한다. 클립보드가 여러 개 있는 것과 같다.\n주요 레지스터 레지스터 설명 \u0026quot;\u0026quot; 기본 레지스터 (마지막 yank/delete) \u0026quot;0 yank 전용 레지스터 \u0026quot;+ 시스템 클립보드 \u0026quot;a ~ \u0026quot;z 이름 지정 레지스터 \u0026quot;_ 블랙홀 레지스터 (버리기) 사용법 \u0026#34;ay a 레지스터에 복사 \u0026#34;ap a 레지스터에서 붙여넣기 \u0026#34;+y 시스템 클립보드에 복사 \u0026#34;+p 시스템 클립보드에서 붙여넣기 \u0026#34;_dd 줄을 삭제하되 레지스터에 저장하지 않음 레지스터 확인: :reg\n팁: dd로 줄을 지우면 기본 레지스터가 덮어씌워진다. 복사한 내용을 유지하려면 \u0026quot;0p(yank 레지스터)를 쓰거나, 삭제 시 \u0026quot;_dd(블랙홀)를 쓰면 된다.\nMarks 특정 위치를 북마크해두고 바로 이동할 수 있다.\nma 현재 위치를 a 마크로 저장 \u0026#39;a a 마크의 줄로 이동 `a a 마크의 정확한 위치로 이동 :marks 모든 마크 목록 소문자(a-z): 파일 내 마크 대문자(A-Z): 파일 간 글로벌 마크 비주얼 블록 모드 Ctrl-v로 사각형 영역을 선택할 수 있다. 여러 줄을 동시에 편집할 때 유용하다.\n여러 줄 앞에 텍스트 추가 1. Ctrl-v 블록 선택 시작 2. j/k로 줄 선택 3. I 줄 앞에 입력 모드 4. 텍스트 입력 5. Esc 모든 선택된 줄에 적용 여러 줄 뒤에 텍스트 추가 1. Ctrl-v 블록 선택 시작 2. j/k로 줄 선택 3. $ 줄 끝까지 선택 확장 4. A 줄 뒤에 입력 모드 5. 텍스트 입력 6. Esc 적용 내장 터미널 Neovim 안에서 터미널을 열 수 있다.\n:terminal 터미널 열기 터미널에서 Normal 모드로 전환: Ctrl-\\ Ctrl-n\n나는 내장 터미널 대신 vim-floaterm 플러그인을 사용한다. Ctrl-;로 플로팅 터미널을 토글할 수 있어서 더 편하다.\n기본 설정 시작하기 Neovim 설정 파일은 ~/.config/nvim/init.lua다. 간단한 설정부터 시작하자:\n-- 줄 번호 표시 vim.opt.number = true vim.opt.relativenumber = true -- 탭 설정 (2칸) vim.opt.tabstop = 2 vim.opt.shiftwidth = 2 vim.opt.expandtab = true -- 검색 설정 vim.opt.ignorecase = true vim.opt.smartcase = true vim.opt.incsearch = true -- 스크롤 여유 vim.opt.scrolloff = 10 -- 마우스 지원 vim.opt.mouse:append(\u0026#34;a\u0026#34;) 이 정도만 설정해도 기본 Neovim보다 훨씬 쾌적해진다.\n다음 글 기본기가 탄탄해졌다면, 플러그인과 LSP를 설정해서 Neovim을 IDE처럼 사용하는 방법을 알아보자.\n→ Neovim 고급: 플러그인과 LSP로 IDE처럼 쓰기\n","permalink":"https://dbalog.dev/posts/neovim-intermediate/","summary":"텍스트 오브젝트, 매크로, 레지스터, 비주얼 블록 등 Neovim을 제대로 쓰기 위한 중급 기능들.","title":"Neovim 중급: 생산성을 높이는 기능들"},{"content":"이 글은 Neovim 시리즈의 첫 번째 글이다.\nNeovim 입문: Vim을 넘어서는 첫걸음 ← 현재 글 Neovim 중급: 생산성을 높이는 기능들 Neovim 고급: 플러그인과 LSP로 IDE처럼 쓰기 나의 Neovim 설정 전체 공개 Neovim이란? Neovim은 Vim의 모던 포크(fork)다. Vim의 철학(모달 편집, 키보드 중심)을 유지하면서 내부를 현대적으로 재구성했다.\nVim과 뭐가 다른가?\nVim Neovim 설정 언어 VimScript Lua (VimScript도 지원) 비동기 처리 제한적 내장 내장 LSP 없음 있음 내장 터미널 기본적 완전한 터미널 에뮬레이터 플러그인 생태계 성숙 빠르게 성장 중 설정 파일 ~/.vimrc ~/.config/nvim/init.lua 한 줄로 요약하면: Vim의 편집 철학 + 현대적인 확장성이다.\n설치 macOS brew install neovim Rocky Linux / CentOS # EPEL에서 설치 dnf install -y epel-release dnf install -y neovim Ubuntu / Debian apt install -y neovim 설치 확인:\nnvim --version 4가지 모드 Neovim(Vim)이 다른 에디터와 근본적으로 다른 점은 모드가 있다는 것이다.\nNormal 모드 Neovim을 열면 이 모드다. 글을 쓰는 게 아니라 글을 조작하는 모드다.\n커서 이동, 삭제, 복사, 붙여넣기 등 다른 모드에서 Esc를 누르면 항상 Normal로 돌아온다 Insert 모드 실제로 글을 타이핑하는 모드다.\n키 동작 i 커서 앞에 입력 a 커서 뒤에 입력 I 줄 맨 앞에 입력 A 줄 맨 뒤에 입력 o 아래에 새 줄 만들고 입력 O 위에 새 줄 만들고 입력 Visual 모드 텍스트를 선택하는 모드다.\n키 동작 v 문자 단위 선택 V 줄 단위 선택 Ctrl-v 블록(사각형) 선택 Command 모드 :를 누르면 하단에 명령줄이 나타난다.\n:w 저장 :q 종료 :wq 저장 후 종료 :q! 저장하지 않고 종료 커서 이동 Normal 모드에서의 이동이 Vim의 핵심이다. 처음에는 어색하지만 손에 익으면 마우스보다 빠르다.\n기본 이동 k h l j 키 방향 h ← 왼쪽 j ↓ 아래 k ↑ 위 l → 오른쪽 팁: j의 아래쪽 꼬리를 생각하면 기억하기 쉽다.\n단어 단위 이동 키 동작 w 다음 단어 시작 b 이전 단어 시작 e 단어 끝 줄 이동 키 동작 0 줄 맨 앞 $ 줄 맨 끝 ^ 줄의 첫 글자 (공백 제외) 화면/파일 이동 키 동작 gg 파일 맨 위 G 파일 맨 아래 Ctrl-d 반 페이지 아래 Ctrl-u 반 페이지 위 {숫자}G 해당 줄로 이동 (예: 42G) 기본 편집 삭제 키 동작 x 커서 위 문자 삭제 dd 줄 삭제 dw 단어 삭제 d$ 또는 D 커서부터 줄 끝까지 삭제 복사 \u0026amp; 붙여넣기 키 동작 yy 줄 복사 (yank) yw 단어 복사 p 커서 뒤에 붙여넣기 P 커서 앞에 붙여넣기 실행 취소 \u0026amp; 다시 실행 키 동작 u 실행 취소 (Undo) Ctrl-r 다시 실행 (Redo) 검색과 치환 검색 /검색어 아래 방향으로 검색 ?검색어 위 방향으로 검색 n 다음 결과 N 이전 결과 치환 :s/old/new/ 현재 줄에서 첫 번째만 :s/old/new/g 현재 줄에서 모두 :%s/old/new/g 파일 전체에서 모두 :%s/old/new/gc 파일 전체, 하나씩 확인하며 파일 열기 # 터미널에서 nvim filename.txt # Neovim 안에서 :e filename.txt :e . 현재 디렉토리 탐색 숫자 + 명령 조합 Vim의 강력한 문법: 숫자 + 동작으로 반복할 수 있다.\n5j 5줄 아래로 3dd 3줄 삭제 4w 4단어 앞으로 10G 10번째 줄로 2yy 2줄 복사 vimtutor로 연습하기 Neovim에는 대화형 튜토리얼이 내장되어 있다:\nnvim +Tutor 30분 정도 따라하면 기본 조작이 손에 익는다. 처음 Neovim을 설치했다면 반드시 한 번은 해보자.\n다음 글 기본 조작이 익숙해졌다면, 텍스트 오브젝트, 매크로, 레지스터 등 생산성을 끌어올리는 기능을 알아보자.\n→ Neovim 중급: 생산성을 높이는 기능들\n","permalink":"https://dbalog.dev/posts/neovim-beginner/","summary":"Neovim이 뭔지, 왜 쓰는지, 그리고 살아남기 위한 최소한의 사용법을 정리한다.","title":"Neovim 입문: Vim을 넘어서는 첫걸음"},{"content":"이 글은 Hugo + PaperMod 블로그 세팅 시리즈의 마지막 글이다.\nHugo + PaperMod로 기술 블로그 만들기 NCP 서버에 Hugo 블로그 배포하기 커스텀 도메인 연결과 Let\u0026rsquo;s Encrypt SSL 설정 ← 현재 글 진행 순서 도메인과 SSL 설정에는 순서가 있다. 순서가 바뀌면 인증서 발급이 실패한다.\n도메인 구매 → DNS A 레코드 설정 → Nginx server_name 변경 → hugo.toml baseURL 변경 → certbot SSL 발급 → 재배포 1. 도메인 구매 도메인 구매처는 여러 곳이 있다:\n업체 특징 Cloudflare 원가 판매, DNS 관리 편리 가비아 국내 최대, 한글 지원 NCP 서버와 같은 곳에서 관리 나는 Cloudflare를 선택했다. DNS 관리 UI가 깔끔하고 추가 비용이 없다.\n.dev 도메인 참고사항 .dev는 Google이 관리하는 TLD로, HSTS preload가 적용되어 있다. 즉:\n모든 .dev 도메인은 브라우저에서 HTTPS만 허용 HTTP로는 접속 자체가 불가 SSL 설정이 필수 기술 블로그에 어울리는 도메인이지만, SSL 없이는 쓸 수 없다는 점을 알고 있어야 한다.\n2. DNS 설정 Cloudflare DNS에서 A 레코드를 추가한다:\nType Name Content Proxy A @ 서버IP DNS only 중요: Proxy를 반드시 꺼야 한다 (회색 구름). Cloudflare 프록시가 켜져 있으면 서버 IP가 Cloudflare로 대체되어 Let\u0026rsquo;s Encrypt 인증서 발급이 복잡해진다.\nDNS 전파 확인:\ndig +short dbalog.dev # 서버IP가 나오면 성공 보통 Cloudflare는 수 분 내로 전파된다.\n3. Hugo 설정 변경 hugo.toml의 baseURL을 변경한다:\n# 변경 전 baseURL = \u0026#34;https://example.com/\u0026#34; # 변경 후 baseURL = \u0026#34;https://dbalog.dev/\u0026#34; 이 값은 sitemap, RSS, 메타 태그 등 블로그 전체에서 사용되므로 반드시 변경해야 한다.\n4. Nginx server_name 변경 deploy/nginx/blog.conf:\nserver_name dbalog.dev; # IP 또는 example.com에서 변경 서버에 반영:\nscp deploy/nginx/blog.conf ncp-blog:/etc/nginx/conf.d/blog.conf ssh ncp-blog \u0026#34;nginx -t \u0026amp;\u0026amp; systemctl reload nginx\u0026#34; 5. Let\u0026rsquo;s Encrypt SSL 인증서 발급 certbot 설치 Rocky Linux 9 기본 저장소에는 certbot이 없다. EPEL 저장소를 먼저 추가한다.\nssh ncp-blog \u0026#34;dnf install -y epel-release\u0026#34; ssh ncp-blog \u0026#34;dnf install -y certbot python3-certbot-nginx\u0026#34; 인증서 발급 certbot의 --nginx 플러그인이 인증서 발급과 Nginx 설정 수정을 한번에 처리한다.\nssh ncp-blog \u0026#34;certbot --nginx -d dbalog.dev \\ --non-interactive \\ --agree-tos \\ --email your@email.com \\ --redirect\u0026#34; --nginx: Nginx 설정을 자동으로 수정 --redirect: HTTP→HTTPS 301 리다이렉트 자동 추가 --non-interactive: 대화형 프롬프트 없이 실행 성공하면 이런 메시지가 나온다:\nSuccessfully received certificate. Certificate is saved at: /etc/letsencrypt/live/dbalog.dev/fullchain.pem Key is saved at: /etc/letsencrypt/live/dbalog.dev/privkey.pem This certificate expires on 2026-07-09. certbot이 Nginx에 추가한 설정 listen 443 ssl; # managed by Certbot ssl_certificate /etc/letsencrypt/live/dbalog.dev/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/dbalog.dev/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; 그리고 HTTP→HTTPS 리다이렉트를 위한 별도 server 블록:\nserver { if ($host = dbalog.dev) { return 301 https://$host$request_uri; } listen 80; server_name dbalog.dev; return 404; } 인증서 자동 갱신 Let\u0026rsquo;s Encrypt 인증서는 90일마다 만료된다. certbot이 systemd 타이머를 자동 등록하므로 수동 갱신이 필요 없다.\n갱신 테스트:\nssh ncp-blog \u0026#34;certbot renew --dry-run\u0026#34; 6. 재배포 baseURL이 변경되었으므로 빌드 후 재배포한다.\n./deploy.sh 확인 # HTTPS 접속 curl -s -o /dev/null -w \u0026#34;%{http_code}\u0026#34; https://dbalog.dev/ # 200 # HTTP → HTTPS 리다이렉트 curl -s -o /dev/null -w \u0026#34;%{http_code}\u0026#34; http://dbalog.dev/ # 301 브라우저에서 https://dbalog.dev/에 접속하면 자물쇠 아이콘이 표시된다.\n시리즈 마무리 3편에 걸쳐 Hugo 블로그를 처음부터 세팅했다:\nHugo + PaperMod 설치 및 설정 NCP 서버에 Nginx + rsync 배포 도메인 연결 + SSL 인증서 이제 남은 건 글을 쓰는 것뿐이다. 마크다운 파일을 만들고, ./deploy.sh를 실행하면 끝.\n","permalink":"https://dbalog.dev/posts/domain-ssl-setup/","summary":"Cloudflare에서 도메인을 구매하고, DNS를 설정하고, Let\u0026rsquo;s Encrypt로 무료 SSL 인증서를 발급하는 과정.","title":"커스텀 도메인 연결과 Let's Encrypt SSL 설정"},{"content":"이 글은 Hugo + PaperMod 블로그 세팅 시리즈의 두 번째 글이다.\nHugo + PaperMod로 기술 블로그 만들기 NCP 서버에 Hugo 블로그 배포하기 ← 현재 글 커스텀 도메인 연결과 Let\u0026rsquo;s Encrypt SSL 설정 배포 구조 [로컬 macOS] [NCP Rocky Linux] content/*.md ↓ hugo --minify public/ ──── rsync ────→ /var/www/blog/ ←── Nginx 서빙 Hugo가 생성한 정적 파일을 rsync로 서버에 전송하고, Nginx가 서빙하는 단순한 구조다. DB도 Node.js도 필요 없다.\n1. SSH 키 인증 설정 매번 비밀번호를 입력하는 건 번거롭고, 스크립트 자동화도 불가능하다. SSH 키 인증을 먼저 세팅한다.\n키 생성 ssh-keygen -t ed25519 -C \u0026#34;blog-deploy\u0026#34; ed25519: RSA보다 짧고 안전한 알고리즘 passphrase: 비워도 되고, 입력하면 macOS Keychain이 기억해준다 서버에 공개키 등록 ssh-copy-id -i ~/.ssh/id_ed25519.pub root@서버IP 이때 마지막으로 서버 비밀번호를 입력한다. 이후로는 비밀번호 없이 접속 가능.\nSSH config 설정 (선택) ~/.ssh/config에 아래를 추가하면 ssh ncp-blog만으로 접속할 수 있다:\nHost ncp-blog HostName 서버IP User root IdentityFile ~/.ssh/id_ed25519 2. Nginx 설정 파일 Hugo 정적 파일을 서빙하기 위한 Nginx 설정이다.\nserver { listen 80; server_name 서버IP; root /var/www/blog; index index.html; # gzip 압축 gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; gzip_min_length 1000; # 정적 파일 캐싱 (30일) location ~* \\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ { expires 30d; add_header Cache-Control \u0026#34;public, immutable\u0026#34;; } # Hugo 정적 사이트 서빙 location / { try_files $uri $uri/ =404; } # 404 페이지 error_page 404 /404.html; location = /404.html { internal; } # 보안 헤더 add_header X-Frame-Options \u0026#34;SAMEORIGIN\u0026#34; always; add_header X-Content-Type-Options \u0026#34;nosniff\u0026#34; always; add_header X-XSS-Protection \u0026#34;1; mode=block\u0026#34; always; } 핵심 설정:\ntry_files $uri $uri/ =404: 파일 → 디렉토리(index.html) → 404 순서로 탐색 gzip: 텍스트 기반 파일을 압축해서 전송 (대역폭 절약) expires 30d: CSS/JS/이미지를 30일 캐싱 (재방문 시 빠른 로딩) 3. 서버 세팅 SSH로 서버에 접속해서 실행한다.\n# Nginx 설치 dnf install -y nginx # 블로그 디렉토리 생성 mkdir -p /var/www/blog # Nginx 설정 파일 복사 (로컬에서) scp deploy/nginx/blog.conf ncp-blog:/etc/nginx/conf.d/blog.conf # 설정 검사 + 시작 ssh ncp-blog \u0026#34;nginx -t \u0026amp;\u0026amp; systemctl enable nginx \u0026amp;\u0026amp; systemctl start nginx\u0026#34; systemctl enable은 서버가 재부팅되어도 Nginx가 자동 시작되게 한다.\n4. 배포 스크립트 매번 명령어를 치는 건 번거로우니 스크립트로 만든다.\ndeploy.sh:\n#!/bin/bash set -e SERVER_USER=\u0026#34;root\u0026#34; SERVER_HOST=\u0026#34;서버IP\u0026#34; SERVER_PORT=\u0026#34;22\u0026#34; REMOTE_DIR=\u0026#34;/var/www/blog\u0026#34; SCRIPT_DIR=\u0026#34;$(cd \u0026#34;$(dirname \u0026#34;$0\u0026#34;)\u0026#34; \u0026amp;\u0026amp; pwd)\u0026#34; cd \u0026#34;$SCRIPT_DIR\u0026#34; build() { echo \u0026#34;=== Hugo 빌드 시작 ===\u0026#34; hugo --minify echo \u0026#34;=== 빌드 완료 ===\u0026#34; } deploy() { echo \u0026#34;=== 서버 배포 시작 ===\u0026#34; rsync -avz --delete \\ -e \u0026#34;ssh -p ${SERVER_PORT}\u0026#34; \\ public/ \\ \u0026#34;${SERVER_USER}@${SERVER_HOST}:${REMOTE_DIR}/\u0026#34; echo \u0026#34;=== 배포 완료 ===\u0026#34; } case \u0026#34;${1:-all}\u0026#34; in build) build ;; deploy) deploy ;; all) build \u0026amp;\u0026amp; deploy ;; *) echo \u0026#34;사용법: $0 {build|deploy|all}\u0026#34; ; exit 1 ;; esac 사용법:\nchmod +x deploy.sh ./deploy.sh build # 빌드만 ./deploy.sh deploy # 배포만 ./deploy.sh # 빌드 + 배포 rsync의 --delete 옵션은 서버에서 로컬에 없는 파일을 삭제한다. 글을 지웠을 때 서버에도 반영되게 하려면 필요하다.\n5. NCP ACG (방화벽) 설정 서버에서 curl localhost는 되는데 외부에서 접속이 안 된다면, NCP ACG 설정을 확인해야 한다.\nNCP 콘솔 → Server → ACG → 인바운드 규칙:\n프로토콜 포트 허용 소스 TCP 80 0.0.0.0/0 TCP 443 0.0.0.0/0 NCP는 OS 레벨 방화벽(firewalld)과 별개로 ACG라는 네트워크 방화벽을 사용한다. 둘 다 확인해야 한다.\n배포 확인 curl -s -o /dev/null -w \u0026#34;HTTP %{http_code}, %{time_total}s\u0026#34; http://서버IP/ # HTTP 200, 0.021s 다음 글 IP 주소로 접속하는 블로그는 아무래도 불편하다. 다음 글에서는 커스텀 도메인을 연결하고 HTTPS를 설정한다.\n→ 커스텀 도메인 연결과 Let\u0026rsquo;s Encrypt SSL 설정\n","permalink":"https://dbalog.dev/posts/ncp-nginx-deploy/","summary":"네이버 클라우드 Rocky Linux 서버에 Nginx를 설치하고 Hugo 블로그를 rsync로 배포하는 과정.","title":"NCP 서버에 Hugo 블로그 배포하기 (Nginx + rsync)"},{"content":"이 글은 Hugo + PaperMod 블로그 세팅 시리즈의 첫 번째 글이다.\nHugo + PaperMod로 기술 블로그 만들기 ← 현재 글 NCP 서버에 Hugo 블로그 배포하기 커스텀 도메인 연결과 Let\u0026rsquo;s Encrypt SSL 설정 왜 Hugo인가? 블로그를 시작하면서 WordPress, Gatsby, Next.js 등 여러 옵션을 검토했다. 최종적으로 Hugo를 선택한 이유:\n빠른 빌드: Go 기반이라 수천 페이지도 수 초 내에 빌드 단순한 배포: 정적 HTML 파일만 서빙하면 되므로 Nginx 하나로 충분 마크다운 기반: .md 파일로 글을 쓰고 git으로 버전 관리 DB 불필요: PHP, Node.js, 데이터베이스 없이 순수 파일만으로 동작 테마는 PaperMod를 선택했다. GitHub Stars 13k+로 Hugo 테마 중 가장 인기가 많고, 다크모드/검색/SEO가 기본 내장되어 있다.\n1. Hugo 설치 macOS 기준 Homebrew로 설치한다.\nbrew install hugo 설치 확인:\nhugo version # hugo v0.160.0+extended+withdeploy darwin/arm64 extended 버전이 설치되어야 SCSS/SASS 처리가 가능하다. Homebrew로 설치하면 자동으로 extended 버전이 들어간다.\nLinux(Rocky/CentOS)에서 설치하려면? Hugo는 Go 바이너리라 패키지 매니저 대신 GitHub Releases에서 직접 다운로드하는 것이 버전 관리에 유리하다.\n2. 새 사이트 생성 mkdir blog \u0026amp;\u0026amp; cd blog hugo new site . --force --force는 빈 디렉토리가 아니어도 생성을 허용하는 플래그다. 실행하면 다음 구조가 만들어진다:\nblog/ ├── archetypes/ # 새 글의 기본 front matter 템플릿 ├── content/ # 마크다운 글 저장 ├── layouts/ # 테마 오버라이드 템플릿 ├── static/ # 정적 파일 (이미지 등) ├── themes/ # 테마 디렉토리 └── hugo.toml # 사이트 설정 파일 3. PaperMod 테마 설치 git submodule로 설치하면 테마 업데이트가 git submodule update 한 줄로 끝난다.\ngit init git submodule add --depth=1 https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod --depth=1: 최신 커밋만 가져와서 용량을 줄인다 (shallow clone) 나중에 테마 업데이트: git submodule update --remote --merge 4. hugo.toml 설정 이 파일이 블로그의 핵심 설정이다. 전체 내용을 공유한다.\nbaseURL = \u0026#34;https://dbalog.dev/\u0026#34; title = \u0026#34;dbalog.dev\u0026#34; paginate = 10 theme = \u0026#34;PaperMod\u0026#34; languageCode = \u0026#34;ko\u0026#34; defaultContentLanguage = \u0026#34;ko\u0026#34; hasCJKLanguage = true enableRobotsTXT = true buildDrafts = false [minify] disableXML = true minifyOutput = true # 검색을 위한 JSON 출력 [outputs] home = [\u0026#34;HTML\u0026#34;, \u0026#34;RSS\u0026#34;, \u0026#34;JSON\u0026#34;] [params] env = \u0026#34;production\u0026#34; title = \u0026#34;dbalog.dev\u0026#34; description = \u0026#34;기술 블로그\u0026#34; keywords = [\u0026#34;Blog\u0026#34;, \u0026#34;Tech\u0026#34;, \u0026#34;Development\u0026#34;] author = \u0026#34;dbalog\u0026#34; DateFormat = \u0026#34;2006년 1월 2일\u0026#34; defaultTheme = \u0026#34;auto\u0026#34; # 시스템 다크/라이트 모드를 따름 disableThemeToggle = false ShowReadingTime = true ShowCodeCopyButtons = true ShowPostNavLinks = true ShowBreadCrumbs = true ShowWordCount = true UseHugoToc = true showtoc = true [params.homeInfoParams] Title = \u0026#34;Welcome\u0026#34; Content = \u0026#34;기술과 개발 이야기를 기록합니다.\u0026#34; [params.fuseOpts] isCaseSensitive = false shouldSort = true location = 0 distance = 1000 threshold = 0.4 minMatchCharLength = 0 limit = 10 keys = [\u0026#34;title\u0026#34;, \u0026#34;permalink\u0026#34;, \u0026#34;summary\u0026#34;, \u0026#34;content\u0026#34;] # 상단 메뉴 [[menu.main]] identifier = \u0026#34;posts\u0026#34; name = \u0026#34;글목록\u0026#34; url = \u0026#34;/posts/\u0026#34; weight = 10 [[menu.main]] identifier = \u0026#34;categories\u0026#34; name = \u0026#34;카테고리\u0026#34; url = \u0026#34;/categories/\u0026#34; weight = 20 [[menu.main]] identifier = \u0026#34;tags\u0026#34; name = \u0026#34;태그\u0026#34; url = \u0026#34;/tags/\u0026#34; weight = 30 [[menu.main]] identifier = \u0026#34;search\u0026#34; name = \u0026#34;검색\u0026#34; url = \u0026#34;/search/\u0026#34; weight = 40 [[menu.main]] identifier = \u0026#34;archives\u0026#34; name = \u0026#34;아카이브\u0026#34; url = \u0026#34;/archives/\u0026#34; weight = 50 # 코드 하이라이팅 [markup] [markup.highlight] noClasses = false 주요 설정 해설 설정 값 왜 필요한가 hasCJKLanguage true 한글 읽기 시간을 글자 수 기반으로 계산 defaultTheme \u0026quot;auto\u0026quot; 시스템 설정에 따라 다크/라이트 자동 전환 outputs.home [\u0026quot;HTML\u0026quot;,\u0026quot;RSS\u0026quot;,\u0026quot;JSON\u0026quot;] JSON을 추가해야 Fuse.js 검색이 동작 ShowCodeCopyButtons true 코드 블록에 복사 버튼 표시 DateFormat \u0026quot;2006년 1월 2일\u0026quot; Go 레퍼런스 타임을 한국식으로 noClasses false Chroma 코드 하이라이팅을 CSS 클래스 기반으로 5. 한글 폰트 + AdSense 준비 PaperMod는 layouts/partials/extend_head.html 파일로 \u0026lt;head\u0026gt; 태그를 확장할 수 있다.\nmkdir -p layouts/partials layouts/partials/extend_head.html:\n{{/* Noto Sans KR 한글 폰트 */}} \u0026lt;link rel=\u0026#34;preconnect\u0026#34; href=\u0026#34;https://fonts.googleapis.com\u0026#34;\u0026gt; \u0026lt;link rel=\u0026#34;preconnect\u0026#34; href=\u0026#34;https://fonts.gstatic.com\u0026#34; crossorigin\u0026gt; \u0026lt;link href=\u0026#34;https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;700\u0026amp;display=swap\u0026#34; rel=\u0026#34;stylesheet\u0026#34;\u0026gt; \u0026lt;style\u0026gt; body { font-family: \u0026#39;Noto Sans KR\u0026#39;, sans-serif; } \u0026lt;/style\u0026gt; 나중에 Google AdSense 승인을 받으면 이 파일에 스크립트를 추가하면 된다.\n주의: layouts/ 디렉토리에는 .html 파일만 넣어야 한다. Hugo가 이 디렉토리의 모든 파일을 Go 템플릿으로 파싱하기 때문에, 마크다운 등 다른 파일을 넣으면 빌드 에러가 발생한다.\n6. 특수 페이지 생성 PaperMod의 검색과 아카이브 기능을 위해 전용 페이지를 만든다.\ncontent/search.md:\n--- title: \u0026#34;검색\u0026#34; layout: \u0026#34;search\u0026#34; placeholder: \u0026#34;검색어를 입력하세요\u0026#34; --- content/archives.md:\n--- title: \u0026#34;아카이브\u0026#34; layout: \u0026#34;archives\u0026#34; url: \u0026#34;/archives/\u0026#34; --- layout 값으로 PaperMod 내장 템플릿이 사용된다.\n7. 첫 번째 포스트 작성 hugo new content posts/hello-world.md front matter 예시:\n--- title: \u0026#34;제목\u0026#34; date: 2026-04-09 draft: false tags: [\u0026#34;Hugo\u0026#34;, \u0026#34;블로그\u0026#34;] categories: [\u0026#34;블로그\u0026#34;] summary: \u0026#34;글 요약\u0026#34; ShowToc: true TocOpen: true --- draft: true이면 hugo server -D에서만 보이고 프로덕션 빌드에서 제외된다 tags와 categories를 지정하면 /tags/, /categories/ 페이지에 자동 집계된다 8. 빌드 및 로컬 확인 # 프로덕션 빌드 hugo --minify # 로컬 미리보기 (draft 포함) hugo server -D # → http://localhost:1313 에서 확인 빌드 결과:\n│ KO ──────────────────┼──── Pages │ 23 Paginator pages │ 0 Non-page files │ 0 Static files │ 0 Processed images │ 0 Aliases │ 6 Total in 61 ms 23페이지가 61ms 만에 생성된다. public/ 디렉토리에 정적 파일이 만들어지고, 이 파일들을 웹서버로 서빙하면 블로그가 된다.\n다음 글 다음 글에서는 이 public/ 디렉토리를 NCP(네이버 클라우드) 서버에 배포하는 과정을 다룬다.\n→ NCP 서버에 Hugo 블로그 배포하기\n","permalink":"https://dbalog.dev/posts/hugo-papermod-setup/","summary":"Hugo와 PaperMod 테마를 사용해 한글 기술 블로그를 처음부터 세팅하는 과정을 정리한다. 설치부터 빌드 검증까지.","title":"Hugo + PaperMod로 기술 블로그 만들기"},{"content":"블로그를 시작하며 이 블로그는 Hugo와 PaperMod 테마를 사용하여 만들었습니다. 정적 사이트 생성기를 선택한 이유와 세팅 과정을 기록합니다.\n왜 Hugo인가? 빠른 빌드 속도: Go 기반이라 수천 개의 페이지도 수 초 내에 빌드 간편한 배포: 정적 파일만 서빙하면 되므로 Nginx만으로 충분 마크다운 기반: .md 파일로 글을 작성하고 git으로 관리 코드 하이라이팅 테스트 Go 코드 예시:\npackage main import \u0026#34;fmt\u0026#34; func main() { fmt.Println(\u0026#34;Hello, Hugo!\u0026#34;) } Python 코드 예시:\ndef greet(name: str) -\u0026gt; str: return f\u0026#34;안녕하세요, {name}님!\u0026#34; print(greet(\u0026#34;dbalog\u0026#34;)) 세팅 시리즈 블로그를 만드는 과정을 시리즈로 정리했다:\nHugo + PaperMod로 기술 블로그 만들기 NCP 서버에 Hugo 블로그 배포하기 커스텀 도메인 연결과 Let\u0026rsquo;s Encrypt SSL 설정 ","permalink":"https://dbalog.dev/posts/hello-world/","summary":"Hugo와 PaperMod 테마로 기술 블로그를 구축한 과정을 정리합니다.","title":"Hugo + PaperMod 블로그 시작하기"}]