# Awesome Life - Full Contents for AI Ingestion > This document contains the full text of the 15 most recent articles from Awesome Life for efficient ingestion by AI search engines and agents. --- ## [나디르 위성영상 캡셔닝을 위한 오픈웨이트 멀티모달 모델 비교](https://butteryoon.github.io/dev/2026/07/17/nadir_captioning_models.html) - **Published Date**: 2026-07-17 - **Author**: James Yoon - **Tags**: llm, multimodal, VLM, remote sensing, captioning, open weight - **Description**: 수직(나디르) 시점 위성영상 캡셔닝에 쓸 수 있는 오픈웨이트 VLM들 — 범용 모델과 원격탐사 특화 모델 비교 ### Content:
나디르(nadir, 수직 하방) 시점 위성영상에 텍스트 캡션을 자동으로 붙이는 작업에 어떤 오픈웨이트 멀티모달 모델을 쓸 수 있는지 조사한 내용을 정리한다. 지난 글에서 MLLM과 임베딩 모델의 역할 차이를 다뤘다면, 이번에는 캡셔닝(MLLM 쪽)에 초점을 맞춘다.
TL;DR: 범용 오픈웨이트 VLM(Qwen3-VL, InternVL3, Gemma 3 등)은 그대로 쓰면 위성영상 도메인 갭 때문에 캡션 품질이 떨어진다. 소규모로 시작한다면 원격탐사 특화 모델(RS-LLaVA, GeoChat, SkyEyeGPT)을 먼저 평가하고, 자체 데이터가 있다면 범용 최신 모델에 LoRA 파인튜닝하는 것이 현재 가장 실용적인 선택이다.
일반 사진과 달리 나디르 시점 영상에는 범용 VLM이 학습한 데이터와의 도메인 갭이 존재한다.
| 모델 | 규모 | 라이선스 | 특징 |
|---|---|---|---|
| Qwen3-VL | 다양한 크기 | Apache 2.0 | Qwen 시리즈 최신, 멀티모달 추론·긴 컨텍스트 강화 |
| Qwen2.5-VL-72B | 72B | Qwen | MMMU ~70%, OCR 강점, 오픈웨이트 최상위권 |
| InternVL3-78B | 78B | MIT | MIT 라이선스 중 최강급 (MMMU ~72%) |
| Gemma 3 | 4B~27B | Gemma | 128K 컨텍스트, 가벼운 배포 |
| Pixtral 12B | 12B | Apache 2.0 | 12B급에서 강력 |
| Phi-4-Multimodal | ~5B | MIT | 소형 클래스 최강, 엣지 배포 후보 |
벤치마크(MMMU, OCRBench 등) 기준으로는 상용 모델과 경쟁하는 수준이지만, 이 점수는 일반 도메인 기준이라 위성영상에 그대로 이어지지 않는다는 점을 유의해야 한다.
| 모델 | 베이스 | 특징 |
|---|---|---|
| RS-LLaVA | LLaVA + LoRA | RS-instructions 데이터셋으로 캡셔닝·VQA 공동 학습 |
| GeoChat | LLaVA-1.5 + LoRA | 영역 지정 질의와 공간 좌표 그라운딩 지원 |
| SkyEyeGPT | 자체 구조 | 8개 RS 데이터셋에서 이미지/영역 캡셔닝 검증, RS 비디오 캡셔닝 지원 |
셋 다 LLaVA 계열의 검증된 레시피(시각 인코더 + 어댑터 + LLM, LoRA 파인튜닝)를 원격탐사 데이터(RSICD 등)에 적용한 것이라, 나디르 영상 캡션의 도메인 어휘(토지 피복, 시설물 유형)를 범용 모델보다 잘 구사한다. 단점은 베이스 LLM이 최신 모델 대비 한 세대 뒤라는 점이다.
원격탐사 캡셔닝의 표준 벤치마크로 RSICD, UCM-Captions, Sydney-Captions 등이 쓰이고, 지표는 BLEU/METEOR/CIDEr가 일반적이다. 다만 자동 지표는 도메인 어휘의 정확성(시설물 오인식 등)을 잘 잡지 못하므로, 실제 도입 시에는 자체 영상 표본에 대한 전문가 검수를 병행하는 것이 안전하다.
지난 이틀간의 대청소(블로그 정리)에 이어, 오늘은 블로그를 Google과 AI 검색 엔진이 잘 읽어갈 수 있는 구조로 손봤다. 작업은 Claude Code와 Gemini 에이전트를 함께 활용했다.
TL;DR: mermaid 다이어그램 렌더러를 테마에 추가하고, 발행 글 19건의 썸네일을 주제에 맞는 이미지로 교체했으며,
llms.txt·JSON-LD 구조화 데이터·robots.txt AI 크롤러 허용까지 적용해 LLM 검색 친화적인 블로그로 만들었다.
멀티 에이전트 구조를 다룬 Caller/Executor 패턴 글을 쓰면서 mermaid 플로우차트를 넣었는데, 이 테마에는 mermaid 렌더러가 없어 코드 블록으로만 표시됐다.
확인해보니 테마의 _includes/javascripts.html이 어느 레이아웃에도 include되어 있지 않았고, 참조하던 assets/js/main.js도 존재하지 않는 테마 잔재였다. 이 파일을 mermaid 로더로 교체하고 default 레이아웃에 include했다. mermaid 블록이 있는 페이지에서만 CDN에서 라이브러리를 로드하므로 다른 페이지에는 부담이 없다.
<script type="module">
const blocks = document.querySelectorAll('pre code.language-mermaid, code.language-mermaid');
if (blocks.length > 0) {
const mermaid = (await import('https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs')).default;
blocks.forEach(code => {
const pre = code.closest('pre') || code;
const div = document.createElement('div');
div.className = 'mermaid';
div.textContent = code.textContent;
pre.replaceWith(div);
});
mermaid.initialize({ startOnLoad: false, theme: 'default' });
await mermaid.run();
}
</script>
발행 글 전체의 썸네일을 인벤토리해보니 범용 플레이스홀더(image-title.jpg)를 쓰거나 아예 이미지가 없는 글이 19건이었다. 3건은 기존 주제별 이미지를 재사용하고, 나머지는 Unsplash에서 주제에 맞는 무료 이미지(키보드, 분석 대시보드, 암호화폐, 서버랙 등) 12종을 받아 교체했다. Unsplash 라이선스는 출처 표기 없이 상업적 사용이 가능하다.
AI 검색 엔진(ChatGPT Search, Perplexity, Gemini 등)이 사이트를 잘 인용하도록 세 가지를 적용했다.
llms.txt는 LLM이 HTML을 긁지 않고도 사이트 구조를 파악할 수 있게 도메인 루트에 두는 커뮤니티 표준 파일이다. Jekyll Liquid 템플릿으로 만들어 빌드할 때마다 자동 갱신되게 했다.
/llms.txt: 사이트 정보 + 최근 40개 글 목록(제목·URL·설명)/llms-full.txt: 최근 15개 글의 전문 — LLM 인덱서가 요청 한 번으로 최신 글을 통째로 가져갈 수 있다---
layout: null
permalink: /llms.txt
---
# {{ site.title }}
## Recent Articles
{% for post in site.posts limit:40 %}
- [{{ post.title }}]({{ site.url }}{{ post.url }}): {{ post.description }}
{% endfor %}
head.html에 글 페이지는 BlogPosting, 그 외는 WebSite 스키마의 JSON-LD를 주입했다. dateModified가 front matter의 last_modified_at과 연동되므로, 글을 수정하고 이 값을 갱신하면 검색엔진에 “살아있는 콘텐츠”라는 신호를 준다.
GPTBot, ChatGPT-User, ClaudeBot, Claude-Web, Google-Extended, PerplexityBot을 명시적으로 허용했다.
배포 후 실제 서빙 상태를 확인했다.
❯ curl -s -o /dev/null -w "%{http_code}" https://butteryoon.github.io/llms.txt
200
/llms.txt(약 9KB), /llms-full.txt(약 84KB) 정상 서빙dateModified·image 매핑 정확AI 검색에 인용되기 좋은 글을 위한 권장사항도 정리해뒀다. 이 글부터 적용해본다.
last_modified_at 갱신: 글을 수정할 때마다 반드시 갱신위성 영상(Remote Sensing) 도메인에서 텍스트 디스크립션을 만들고 특징을 추출하는 방법을 조사하면서, 멀티모달 LLM과 이미지 임베딩 모델(RemoteCLIP, GeoRSCLIP 등)의 차이를 정리해봤다. 두 접근은 목적과 작동 방식, 성능 특성이 뚜렷하게 다르다.
이미지(위성 영상)와 텍스트 입력을 결합해 시각적 추론을 수행하고, 최종적으로 자연어 문장을 생성하는 거대 모델이다.
대조 학습(Contrastive Learning)으로 위성 영상과 텍스트를 같은 벡터 공간에 매핑해 특징을 추출하는 모델이다.
| 비교 항목 | 멀티모달 LLM | 임베딩 모델 (RemoteCLIP 등) |
|---|---|---|
| 출력 형태 | 자연어 텍스트 | 고정 차원 벡터 (512, 768차원 등) |
| 자연어 생성 | 가능 (맥락 해석 후 새 문장 생성) | 불가능 (텍스트 후보와 유사도 비교만) |
| 주요 태스크 | VQA, 종합 분석·요약 | 제로샷 분류, 유사 이미지 검색 |
| 연산량 | 수십억~수백억 파라미터, 무거움 | 수억 개 이하, 추론 속도 빠름 |
| 데이터 활용 | 고수준 맥락 추론·묘사 | 대규모 DB 인덱싱·고속 검색 |
MLLM은 시각 인코더로 영상의 시각 토큰을 뽑고, 어댑터(Projection Layer)를 거쳐 LLM의 텍스트 임베딩 공간으로 투영한 뒤, 프롬프트와 함께 자기회귀(Autoregressive) 방식으로 한 토큰씩 문장을 생성한다.
결과물 예시: “해당 위성 이미지는 전형적인 도심 주거 지역으로, 적색 지붕의 단독 주택들이 격자형 도로망을 따라 조밀하게 밀집해 있습니다.”
CLIP 계열은 이미지 인코더와 텍스트 인코더의 투 타워(Two-tower) 구조다. 영상과 텍스트를 각각 벡터로 바꾸고, 학습 시 두 벡터의 코사인 유사도를 극대화하도록 정렬한다.
결과물 예시: 영상 입력 시
[0.12, -0.45, 0.89, ...]같은 벡터를 반환하고, 이 벡터는 “항구 시설”이라는 텍스트 벡터와 가장 가까운 거리에 놓인다.
두 모델은 상호 배타적이지 않고, 실제 시스템에서는 보완적으로 조합하는 것이 이상적이다.
임베딩 모델이 넓게 거르고 MLLM이 깊게 읽는 구조라, 비용과 속도, 품질의 균형을 맞출 수 있다.
멀티 에이전트 시스템을 만들면서 정리한 Caller/Executor 패턴 구조를 기록해둔다. 사용자 요청을 받는 에이전트(Caller)와 실제 작업을 수행하는 전문 에이전트(Executor)를 분리하고, 둘 사이를 MCP(Model Context Protocol)로 연결하는 구성이다.
역할은 둘로 나뉜다.
핵심은 Caller가 Executor를 직접 HTTP로 호출하지 않는다는 점이다. Executor는 Foundry Toolbox를 통해 하나의 tool로 노출되고, Caller의 LLM이 질문을 보고 그 tool을 쓸지 말지 판단한다.
Caller 진입점(main())에서 하는 일은 크게 세 가지다: tool 로드 → 에이전트 생성 → 서버 실행.
| 순서 | 함수/객체 | 역할 |
|---|---|---|
| 1 | main() |
진입점 |
| 2 | _load_toolbox_tools() |
Toolbox에서 tool 로드 |
| 3 | DefaultAzureCredential() |
Azure 인증 객체 생성 |
| 4 | get_bearer_token_provider() |
토큰 공급자 생성 |
| 5 | _ToolboxAuth |
매 요청마다 최신 Bearer 토큰 주입 |
| 6 | MultiServerMCPClient(...) |
Toolbox MCP 연결 |
| 7 | client.get_tools() |
tool 목록 가져오기 |
| 8 | _build_chat_model() |
Azure AI Foundry LLM 모델 객체 생성 |
| 9 | create_agent(...) |
LangGraph 에이전트 생성 |
| 10 | ResponsesHostServer(graph).run(...) |
서버 실행 |
인증 처리가 눈여겨볼 부분인데, 토큰이 만료될 수 있으므로 고정 토큰을 쓰지 않고 _ToolboxAuth 같은 인증 객체가 매 요청마다 최신 Bearer 토큰을 받아 주입하는 구조로 만든다.
사용자가 질문을 보내면 다음 순서로 동작한다.
ResponsesHostServer)에 들어옴math_expert)을 선택flowchart TD
A[사용자 요청] --> B[ResponsesHostServer]
B --> C[LangGraph Agent]
C --> D[LLM 모델]
C --> E[Tool 목록]
E --> F[math_expert tool]
F --> G[Toolbox MCP Endpoint]
G --> H[Executor Agent]
H --> I[전문 답변 결과]
I --> F
F --> C
C --> J[최종 응답]
J --> K[사용자에게 반환]
온프레미스 서비스로 오픈웨이트 모델을 사용하려 할 때 GPU 인프라가 충분하지 않으면 여러가지 모델을 하기 어렵다. 이렇게 LLM 모델별 API로 테스트해보고 싶은데 비용이 부담될 때 OpenRouter의 무료 모델을 쓸 수 있다. 여러 프로바이더의 모델을 하나의 OpenAI 호환 API로 호출할 수 있고, 모델 ID에 :free가 붙은 모델은 과금 없이 사용할 수 있다.
OpenRouter는 OpenAI, Anthropic, Google, Meta 등 여러 프로바이더의 모델을 하나의 API 엔드포인트로 중계해주는 서비스다. OpenAI SDK와 호환되기 때문에 base URL과 API 키만 바꾸면 기존 코드를 그대로 쓸 수 있다.
sk-or-v1-... 키를 환경변수로 저장# PowerShell
[System.Environment]::SetEnvironmentVariable("OPENROUTER_API_KEY", "<발급받은 키>", "User")
# bash
export OPENROUTER_API_KEY="<발급받은 키>"
모델 목록 페이지에서 가격 필터를 FREE로 설정하면 무료 모델만 볼 수 있다. 아래처럼 API로도 확인할 수 있는데, 모델 ID가 :free로 끝나는 것들이다.
curl -s https://openrouter.ai/api/v1/models | \
jq -r '.data[].id | select(endswith(":free"))'
이 글을 쓰는 시점(2026-07-15) 기준으로 사용할 수 있는 무료 모델은 20개다. Llama 3.3 70B, Qwen3 Coder 480B, NVIDIA Nemotron 3 Ultra 550B 같은 대형 모델도 무료로 테스트할 수 있다.

API의 모델명에 “openrouter/free”로 적으면 프리 모델중에 인풋에 알맞는 모델을 선택해서 응답을 해준다. ( 어떤 모델이 선택됐는지 알 수는 없다 )
curl https://openrouter.ai/api/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-d '{
"model": "meta-llama/llama-3.3-70b-instruct:free",
"messages": [
{"role": "user", "content": "안녕하세요. 자기소개 해주세요."}
]
}'
from openai import OpenAI
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=os.environ["OPENROUTER_API_KEY"],
)
resp = client.chat.completions.create(
model="qwen/qwen3-coder:free",
messages=[{"role": "user", "content": "파이썬으로 퀵정렬 구현해줘"}],
)
print(resp.choices[0].message.content)
무료 모델에는 요청 횟수 제한이 있다 (2026년 7월 기준).
| 구분 | 분당 요청 | 일일 요청 |
|---|---|---|
| 기본 | 20회 | 50회 |
| 크레딧 $10 이상 구매 이력 | 20회 | 1,000회 |
$10만 한 번 충전해두면 하루 1,000회까지 늘어나므로, 본격적으로 테스트할 계획이라면 최소 크레딧을 넣어두는 것이 편하다. 무료 모델은 프로바이더 사정에 따라 목록이 자주 바뀌고, 입력한 데이터가 학습에 사용될 수 있다는 점도 유의해야 한다.
VSCode AI TOOLS 플러그인에 커스텀 모델로 오픈라우터 프리모델을 등록하고 플레이그라운에서 여러모델을 비교하며 가장 적합한 모델을 찾을 수 있다.
새 PC를 세팅할 때마다 PowerShell 프로파일을 다시 만들게 되는데, 지금 쓰고 있는 프로파일을 기준으로 기본 설정 방법을 정리해둔다.
PowerShell 7(pwsh) 기준으로 프로파일 경로는 $PROFILE 변수로 확인할 수 있다.
❯ $PROFILE
C:\Users\<사용자>\Documents\PowerShell\Microsoft.PowerShell_profile.ps1
파일이 없으면 만들어주고, 편집기로 연다.
if (-not (Test-Path $PROFILE)) { New-Item -ItemType File -Path $PROFILE -Force }
nvim $PROFILE # 또는 notepad $PROFILE
프로파일은 PowerShell이 시작될 때마다 실행되는 스크립트라서, 여기에 프롬프트 테마·별칭·함수를 넣어두면 매 세션에서 바로 쓸 수 있다.
oh-my-posh로 프롬프트를 꾸민다. winget으로 설치하고 프로파일에 초기화 한 줄만 넣으면 된다.
winget install JanDeDobbeleer.OhMyPosh
# $PROFILE
oh-my-posh init pwsh | Invoke-Expression
테마를 지정하고 싶으면 --config에 테마 파일을 넘긴다. 아이콘이 깨지면 Nerd Font를 설치하고 터미널 글꼴로 지정해야 한다.
oh-my-posh init pwsh --config "$env:POSH_THEMES_PATH\atomic.omp.json" | Invoke-Expression
PowerToys의 Command Not Found 기능을 켜면, 없는 명령어를 입력했을 때 winget으로 설치할 수 있는 패키지를 추천해준다. PowerToys 설정에서 활성화하면 프로파일에 아래 블록이 자동으로 추가된다.
Import-Module -Name Microsoft.WinGet.CommandNotFound
PowerShell은 curl, cat 같은 유닉스 명령어를 자체 cmdlet의 별칭으로 잡아놔서, 진짜 curl이나 bat 같은 도구를 쓰려면 기본 별칭을 먼저 지워야 한다.
# 기본 별칭 제거
if ( Test-Path Alias:curl ) { Remove-Item Alias:curl }
if ( Test-Path Alias:cat ) { Remove-Item Alias:cat }
# 새 별칭
Set-Alias vi nvim
Set-Alias cat bat
Set-Alias magick -Value "C:\Program Files\ImageMagick-7.1.1-Q16-HDRI\magick.exe"
자주 가는 디렉토리는 함수로 만들어두면 편하다.
Function cwork { Set-Location "C:\work\workspace\" }
Function cdesk { Set-Location "$env:USERPROFILE\Desktop" }
Function cdocs { Set-Location "$env:USERPROFILE\Documents" }
API 키 같은 환경변수도 프로파일에서 등록할 수 있다. "User" 스코프로 지정하면 사용자 환경변수로 저장된다.
[System.Environment]::SetEnvironmentVariable("MY_API_KEY", "<발급받은 키>", "User")
프로파일을 dotfiles 저장소 등에 공개할 계획이 있다면 키 값을 프로파일에 직접 적지 말고 별도 파일로 분리하는 것이 안전하다.
리눅스 습관대로 쓰거나 자주 확인하는 정보는 함수로 정리해뒀다.
Function ver { Write-Output $PSVersionTable } # 버전 확인
Function export { Get-ChildItem -Path Env:\ } # 환경변수 목록
Function Get-IPinfo { ipconfig | Select-String IPv4 } # 내부 IP
Function Get-MyIPinfo { curl "http://api.ipify.org?format=json" } # 공인 IP
Function uptime { Get-WmiObject -Class Win32_OperatingSystem |
Select-Object @{n='LastBoot';e={$_.ConvertToDateTime($_.LastBootUpTime)}} }
Function Get-Title { Get-Process | Where-Object {$_.mainWindowTitle} |
Format-Table Id, Name, mainWindowTitle -AutoSize } # 창 있는 프로세스
Function Get-WiFi-Profile { netsh wlan show profile } # 저장된 Wi-Fi 목록
SSH 역터널을 함수로 만들어두면 원격 접속 준비도 한 단어로 끝난다.
Function oc01 { ssh -R 53022:localhost:22 oc01 }
프로파일을 수정한 뒤에는 새 세션을 열거나 다시 로드한다.
. $PROFILE
2023년에 작성해두었던 초안을 보완해서 발행한다. 방화벽으로 접속이 제한된 네트워크 안의 PC나 로컬에서 개발 중인 서비스를 외부에서 접속해야 할 때 사용할 수 있는 터널링 도구들을 정리해본다.
회사나 집의 PC는 대부분 공유기나 방화벽 뒤에 있어서 외부에서 직접 접속할 수 없다. 포트포워딩이나 VPN을 구성하면 되지만 네트워크 관리 권한이 없거나 설정이 번거로운 경우가 많다. 이럴 때 터널링 도구를 쓰면 내부에서 외부의 중계 서버로 아웃바운드 연결을 만들어두고, 그 연결을 통해 거꾸로 내부 서비스에 접속할 수 있다.
ngrok은 이 분야에서 가장 유명한 도구다. 로컬에서 ngrok http 8080 한 줄이면 https://xxxx.ngrok-free.app 같은 공개 URL이 만들어져서 웹훅 테스트나 데모 공유에 특히 편하다.
Cloudflare Tunnel은 cloudflared 데몬을 내부 PC에 설치해서 Cloudflare 네트워크로 아웃바운드 터널을 만드는 방식이다.
myapp.example.com 같은 주소로 내부 서비스를 무료로 상시 노출할 수 있다.cloudflared tunnel --url http://localhost:8080 한 줄로 trycloudflare.com 임시 URL도 만들 수 있다(Quick Tunnel).Tailscale은 위 두 가지와 성격이 조금 다르다. 특정 포트를 공개 URL로 노출하는 게 아니라, 내 디바이스들끼리 WireGuard 기반의 프라이빗 네트워크(메시 VPN)를 만들어준다. 각 디바이스에 100.x.x.x 대역의 IP가 할당되고, 어디에 있든 서로 직접 접속할 수 있다.
외부의 불특정 다수에게 서비스를 보여주는 용도보다는 “내 PC들끼리 어디서나 SSH/RDP로 붙는” 용도에 가장 잘 맞는다. 자세한 사용법은 예전에 정리한 TailScale을 이용한 개인 네트워크 구성하기 글을 참고한다.
초안을 쓸 당시 관심을 가졌던 tunnelto.dev는 Rust로 만들어진 오픈소스 터널링 도구인데, 2026년 현재도 서비스는 살아있다. 무료 플랜이 있고 고정 서브도메인은 월 $4부터다. 다만 ngrok이나 Cloudflare Tunnel에 비해 커뮤니티나 기능 발전이 활발한 편은 아니라서, 지금 새로 시작한다면 아래 세 가지 중에서 고르는 것을 추천한다.
| 도구 | 무료 여부 | 방식 | 주 용도 |
|---|---|---|---|
| ngrok | 무료(랜덤 도메인, 제한 있음) | 공개 URL 터널 | 웹훅 테스트, 임시 데모 |
| Cloudflare Tunnel | 무료(자기 도메인 필요, Quick Tunnel은 도메인 불필요) | 공개 URL 터널 | 개인 서비스 상시 노출 |
| Tailscale | 무료(3 사용자 / 100 디바이스) | 메시 VPN | 내 디바이스 간 SSH/RDP 원격 접속 |
정리하면, 잠깐 보여줄 거면 ngrok, 도메인 걸고 계속 운영할 거면 Cloudflare Tunnel, 내 장비들 원격 접속용이면 Tailscale이다.
2021년에 OpenSIPS 2.4와 RTPEngine mr7.4 조합을 메모해둔 초안을 보완해서 발행한다. 오픈소스로 VOIP 시스템을 만들 때 쓰이는 주요 구성요소를 현재(2026년) 기준으로 정리해본다.
VOIP 시스템은 크게 두 축으로 나뉜다. 호 설정/해제를 담당하는 시그널링(SIP) 과, 실제 음성 패킷을 나르는 미디어(RTP) 다. 오픈소스 세계에서는 이 둘을 별도 컴포넌트로 분리해 조합하는 구성이 일반적이다.
정리하면, 대량 호 라우팅은 OpenSIPS 또는 Kamailio + RTPEngine 조합, 통화 기능(IVR/PBX)이 중심이면 FreeSWITCH나 Asterisk를 조합하는 것이 일반적인 오픈소스 VOIP 스택이다.
2020년에 gist만 올려두고 묵혀둔 초안을 보완해서 발행한다. 스크립트나 배치 작업이 끝났을 때 Slack 채널로 알림을 받고 싶다면 Incoming Webhook + curl 조합이 가장 간단하다.
https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXX 형태의 Webhook URL이 생성된다.이 URL은 사실상 비밀 토큰이다. URL만 알면 누구나 채널에 글을 쓸 수 있으니 공개 저장소에 커밋하지 말고 환경변수 등으로 관리한다. 유출됐다면 앱 대시보드에서 revoke 하고 새로 발급받으면 된다.
curl -X POST -H 'Content-type: application/json' \
--data '{"text":"빌드 완료! :tada:"}' \
https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXX
성공하면 응답으로 ok가 온다. 줄바꿈은 \n, 이모지는 :tada: 같은 코드로 넣을 수 있고, 더 예쁘게 꾸미고 싶으면 blocks 페이로드(Block Kit)를 쓰면 된다.
쉘 스크립트에서 쓸 때는 이렇게 함수로 만들어두면 편하다.
SLACK_WEBHOOK_URL="https://hooks.slack.com/services/..."
notify() {
curl -s -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"$1\"}" "$SLACK_WEBHOOK_URL"
}
notify "백업 작업 완료: $(date '+%F %T')"
예전에 만들어둔 PowerShell/curl 예제 gist.
2020년에 mutt 설치 로그만 붙여놓고 방치했던 초안을 보완해서 발행한다. WSL Ubuntu 터미널에서 스크립트 결과를 메일로 받고 싶을 때, 로컬에 postfix 같은 MTA를 세팅하는 것보다 msmtp로 Gmail SMTP에 릴레이하는 쪽이 훨씬 간단하고 요즘 방식이다.
예전 초안에서는 mutt를 설치하다가 딸려오는 postfix까지 세팅했는데, WSL에서 postfix를 직접 굴리면 가정용 IP라 대부분 스팸 처리되고 관리도 번거롭다. msmtp는 가벼운 SMTP 클라이언트라 설정 파일 하나로 Gmail 계정을 통해 메일을 보낼 수 있다.
$ sudo apt update
$ sudo apt install msmtp msmtp-mta mutt
...
The following NEW packages will be installed:
msmtp msmtp-mta mutt libtokyocabinet9
Do you want to continue? [Y/n] Y
msmtp-mta를 같이 설치하면 sendmail 명령이 msmtp로 연결되어 mutt나 cron이 그대로 쓸 수 있다.
Gmail은 일반 비밀번호로는 SMTP 로그인이 안 된다. 구글 계정에 2단계 인증을 켠 뒤 앱 비밀번호 페이지에서 16자리 앱 비밀번호를 만들어 쓴다.
~/.msmtprc 파일을 만든다.
defaults
auth on
tls on
tls_starttls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
logfile ~/.msmtp.log
account gmail
host smtp.gmail.com
port 587
from myid@gmail.com
user myid@gmail.com
password 발급받은앱비밀번호16자리
account default : gmail
비밀번호가 들어 있으니 권한을 꼭 조인다.
$ chmod 600 ~/.msmtprc
msmtp 단독으로도 보낼 수 있고,
$ echo -e "Subject: WSL test mail\n\n백업 스크립트 완료" | msmtp jsyoon@example.com
첨부파일이 필요하면 mutt를 쓴다. ~/.muttrc에 한 줄만 추가하면 mutt가 msmtp로 발송한다.
set sendmail = "/usr/bin/msmtp"
set use_from = yes
set realname = "ButterYoon"
set from = "myid@gmail.com"
한 줄 발송 예제.
$ echo "본문 내용" | mutt -s "제목" -a result.log -- jsyoon@example.com
cron 작업 끝에 붙여두면 WSL에서도 작업 결과를 메일로 받아볼 수 있다.
2021년에 링크만 모아두고 방치했던 초안을 보완해서 발행한다. 사이드프로젝트를 시작할 때 데이터 소스로 바로 쓸 수 있는 무료 OPEN API들을 무료 한도와 함께 정리했다.
| API | 한줄 설명 | 무료 한도 |
|---|---|---|
| OpenWeather | 현재 날씨, 예보, 대기오염 등 날씨 데이터 API | 60 calls/min, 월 100만 콜 수준. 이메일만으로 API 키 발급 |
| geo.ipify (IP Geolocation) | IP 주소로 국가/도시/좌표/ISP를 조회하는 API | 월 1,000회 무료 lookup |
| Unsplash API | 고해상도 무료 사진 검색/랜덤 이미지 API | 데모 모드 시간당 50회, 프로덕션 승인 시 시간당 5,000회 |
| Open-Meteo | API 키 없이 바로 쓰는 오픈소스 날씨 API | 비상업적 용도 무료, 키 발급조차 필요 없음 |
| TMDB | 영화/TV 프로그램 메타데이터, 포스터 이미지 API | 비상업적 용도 무료 (API 키 발급) |
| ExchangeRate-API | 환율 데이터 API | Open access는 키 없이, 무료 플랜 월 1,500회 |
간단히 특징을 보면,
2020년에 초안으로 남겨두었던 글을 보완해서 발행한다. 네이버 검색에 블로그가 노출되도록 네이버 서치어드바이저에 사이트를 등록하고, 소유확인과 사이트맵 제출까지 진행하는 과정을 정리한다.
https://searchadvisor.naver.com에 접속해서 네이버 계정으로 로그인한 뒤, 상단의 웹마스터 도구로 들어간다. 사이트 URL 입력란에 https://를 포함한 전체 주소를 입력하고 등록한다.
등록 직후 소유확인 단계가 진행된다. 두 가지 방법 중 하나를 선택하면 된다.
<meta name="naver-site-verification" content="..."> 형태의 태그를 사이트 <head> 영역에 추가한다. 파일 업로드가 번거로운 환경이라면 이 방법이 간편하다.적용 후 소유확인 버튼을 누르면 검증이 완료된다.
소유확인이 끝나면 왼쪽 메뉴의 요청 > 사이트맵 제출로 이동해서 sitemap.xml 경로를 입력하고 확인을 누른다. Jekyll 블로그는 jekyll-sitemap 플러그인을 쓰면 사이트맵이 자동 생성된다. RSS를 쓴다면 요청 > RSS 제출에서 feed.xml도 함께 제출해 두면 좋다. 제출 후 실제 검색 노출까지는 며칠에서 2주 정도 걸릴 수 있다.
서치어드바이저에는 사이트의 검색 최적화 상태를 빠르게 점검해주는 사이트 간단 체크 도구가 있다. URL을 입력하면 robots.txt, 사이트 제목/설명, OpenGraph 태그 등의 상태를 확인해준다.

체크 결과에서 사이트 제목·설명이나 OpenGraph 항목이 비어 있다면 <head>에 title, description 메타태그와 og:title, og:description 태그를 채워주자. Jekyll이라면 jekyll-seo-tag 플러그인으로 한 번에 해결할 수 있다.
2022년에 작성해두었던 초안을 보완해서 발행한다. MetaMask 지갑에 Polygon PoS 네트워크를 수동으로 추가하는 방법을 정리해본다.
MetaMask는 가장 널리 쓰이는 이더리움 계열 지갑이다. 공식 다운로드 페이지에서 브라우저 확장(Chrome, Firefox, Edge 등)이나 모바일 앱을 설치하고 지갑을 생성한다. 시드 문구(Secret Recovery Phrase)는 절대 온라인에 저장하지 말고 안전한 곳에 보관한다.
MetaMask는 기본적으로 이더리움 메인넷만 설정되어 있어서 Polygon 같은 다른 네트워크는 직접 추가해야 한다. 최근 버전에서는 네트워크 선택 목록에 Polygon이 기본 제공 목록으로 떠서 클릭 한 번으로 추가되는 경우도 있지만, 없다면 아래처럼 수동으로 추가한다.
MetaMask에서 좌측 상단의 네트워크 선택 드롭다운을 열고 Add network → Add a network manually를 선택한 뒤 아래 값을 입력한다.
| 항목 | 값 |
|---|---|
| Network name | Polygon PoS |
| New RPC URL | https://polygon-rpc.com |
| Chain ID | 137 |
| Currency symbol | POL |
| Block explorer URL | https://polygonscan.com |
저장하면 네트워크 목록에 Polygon이 추가되고, 선택하면 바로 전환된다.
참고로 Polygon의 네이티브 토큰은 원래 MATIC이었는데 2024년에 POL로 리브랜딩되었다. 예전 가이드에는 통화 기호가 MATIC으로 나와 있지만 현재는 POL을 사용한다.
초안에 적어두었던 예전 문서 링크는 현재 docs.polygon.technology의 MetaMask 가이드로 옮겨졌으니 최신 값은 공식 문서를 확인하는 것이 좋다.
2020년에 ffmpeg로 SRT 스트리밍을 테스트하면서 적어둔 초안을 보완해서 발행한다. mp4 파일을 ffmpeg로 읽어 SRT 프로토콜로 전송하고, ffplay로 받아서 재생하는 가장 단순한 구성을 정리한다.
SRT(Secure Reliable Transport)는 Haivision이 공개한 오픈소스 저지연 스트리밍 프로토콜이다. UDP 기반이지만 패킷 재전송(ARQ)과 타임스탬프 기반 전송으로 불안정한 공용 인터넷 구간에서도 안정적인 전송이 가능하고, AES 암호화도 지원한다. RTMP를 대체하는 기여(contribution)용 프로토콜로 방송 업계에서 많이 쓰이고 있으며, OBS Studio도 SRT 송출/수신(ingest)을 지원한다.
SRT 연결에는 세 가지 모드가 있다.
ffmpeg의 SRT URL 형식은 srt://hostname:port?option1=value1&option2=value2 이고, mode 외에도 latency(재전송 버퍼 지연), streamid, pkt_size, passphrase(암호화) 같은 옵션을 쿼리스트링으로 지정할 수 있다.
mp4 파일을 실시간 속도(-re)로 읽어서 재인코딩 없이(-c copy) MPEG-TS 컨테이너로 감싸 SRT listener로 대기시킨다.
❯ ffmpeg -re -i .\BigBuckBunny.mp4 -c copy -f mpegts "srt://:8888?mode=listener"
-re : 파일을 실제 재생 속도로 읽는다. 라이브 스트리밍 흉내를 낼 때 필수다.-c copy : 트랜스코딩 없이 스트림을 그대로 복사한다.-f mpegts : SRT는 보통 MPEG-TS를 실어 나르므로 컨테이너를 mpegts로 지정한다.mode=listener : 8888 포트를 열고 클라이언트 접속을 기다린다. 호스트를 생략하면 모든 인터페이스에 바인딩된다.다른 터미널에서 ffplay로 접속해서 재생한다. mode를 생략하면 기본값이 caller라서 그냥 주소만 주면 된다.
❯ ffplay srt://localhost:8888
반대로 ffmpeg 쪽을 caller로, 수신 쪽(ffplay 또는 OBS의 Media Source)을 listener로 구성할 수도 있다. 예를 들어 OBS에서 srt://0.0.0.0:8888?mode=listener로 입력 소스를 만들어 두고, ffmpeg가 srt://obs호스트:8888 로 밀어 넣는 식이다.
2020년에 초안으로 남겨두었던 글을 보완해서 발행한다. Microsoft Bing 검색에 블로그가 노출되도록 Bing 웹마스터 도구(Bing Webmaster Tools)에 사이트를 등록하고 사이트맵을 제출하는 과정을 정리한다.
https://www.bing.com/webmasters에 접속해서 로그인한다. Microsoft 계정 외에 Google, Facebook 계정으로도 로그인할 수 있다.
사이트를 추가하는 방법은 두 가지다.

이미 구글 서치 콘솔에 사이트를 등록해 두었다면 이 방법이 가장 간단하다. 가져오기(Import) 버튼을 누르고 Google 계정으로 로그인해 권한을 허용하면, 서치 콘솔에 등록된 사이트 목록이 나온다. 가져올 사이트를 선택하면 별도의 소유 확인 절차 없이 등록이 끝난다. 서치 콘솔에 제출해 둔 사이트맵도 함께 가져온다.
수동 등록을 선택하면 사이트 URL을 입력한 뒤 소유 확인을 진행해야 한다. XML 파일(BingSiteAuth.xml) 업로드, HTML 메타태그 삽입, DNS CNAME 레코드 추가 중 하나를 선택할 수 있다. GitHub Pages 블로그라면 메타태그를 <head>에 추가하는 방법이 편하다.

등록이 끝나면 대시보드 왼쪽 메뉴의 사이트맵(Sitemaps) 에서 https://내도메인/sitemap.xml 주소를 제출한다. Google Search Console에서 가져오기로 등록했다면 사이트맵이 이미 들어와 있을 수 있으니 목록만 확인하면 된다. 제출 후 수집까지는 보통 몇 시간에서 며칠 정도 걸린다.
URL 검사 도구로 개별 페이지의 색인 상태를 확인할 수 있고, 검색 성능 리포트에서 노출/클릭 통계도 볼 수 있다. 구글 서치 콘솔에 등록해 둔 사이트라면 가져오기 기능 덕분에 몇 분이면 끝나니 함께 등록해 두자.