noti — Discord Webhook 통합 Bash 알림
작업 결과를 Discord Webhook으로 보내는 단일 바이너리(스크립트).
텍스트/임베드/파일 업로드/명령 실행 결과 요약을 전송하고, 프리셋(.noti.preset) 으로 커맨드를 짧게 별칭처럼 쓸 수 있어.
특징 (Features)
send텍스트,embed카드,file업로드,run실행/요약 전송 지원- 프리셋(.noti.preset):
noti <이름> <args...>로 재사용 (git alias 느낌,$1 $2 $@자리표시자) - 성공/실패/소요시간/시작/종료 시각 자동 포함 (run)
- 로그 파일 자동 첨부 옵션(실패시에만/항상)
- 멘션 차단 기본값,
--allow-mentions로 필요한 경우만 허용 - 429 rate limit 자동 재시도, 5xx 지수 백오프
- 디버그 모드(마스킹된 URL, 페이로드 프리뷰)
- Linux/macOS/WSL에서 동작(표준
bash,curl필요),jq설치 시 임베드 품질↑
요구사항
- bash 4+ (연관 배열 사용),
curl필수 - 선택:
jq(JSON 생성/임베드/필드 처리 안정)
설치
# noti 파일을 PATH에 둔다 (예: /usr/local/bin)
sudo install -m 0755 noti /usr/local/bin/noti
# 실행 확인
noti help빠른 시작 (Quick Start)
# 1) Webhook 설정 (환경변수 or .noti.conf 중 하나)
export NOTI_WEBHOOK='https://discord.com/api/webhooks/.../...'
# 2) 간단 메시지
noti send "hello from noti 👋"
# 3) 임베드(색/필드)
noti embed --title "Deploy" --desc "✅ Success" --color 3066993 --field Service api --field Version v1.2.3
# 4) 명령 실행 후 결과 전송 (실패 시 로그 첨부)
noti run --title "CI: test" --attach-log-on-fail -- bash -c 'echo ok; exit 1'설정 (Configuration)
1) Webhook 제공 방식 (우선순위)
- 환경변수
NOTI_WEBHOOK - 설정 파일
.noti.conf- 탐색 순서:
$NOTI_CONFIG→./.noti.conf→~/.noti.conf - 포맷(택1 키):
webhook = https://discord.com/api/webhooks/xxx/yyy # 또는 NOTI_WEBHOOK = https://discord.com/api/webhooks/xxx/yyy # 또는 webhook_url = https://discord.com/api/webhooks/xxx/yyy
- 탐색 순서:
보안 팁: Webhook URL은 토큰. 저장소에 커밋 금지. 노출 시 Discord에서 재발급/기존 삭제 권장.
2) 프리셋 파일 .noti.preset
- 탐색 순서:
$NOTI_PRESET→./.noti.preset→~/.noti.preset - 형식: 한 줄에 이름 = 템플릿 또는 이름: 템플릿
- 템플릿 안에서
$1 $2 $@사용해 인자 바인딩 (git alias와 동일한 감각) - 예약어 금지:
send,embed,file,run,help,-h,--help - 예시:
# .noti.preset deploy = embed --title "Deploy" --desc "$1" --color 3066993 --field Service api --field Version "$2" ping = send "[PING] $1" test-fail = run --title "CI: $1" --attach-log-on-fail -- bash -lc "$2" tarball = run --title "Tar $1" -- bash -lc 'tar -czf "$2" ${@:3}'
- 사용:
noti deploy "✅ 성공" v1.2.3 noti ping "서버 체크" noti test-fail unit 'make test' noti tarball v1.2.3 build/out.tgz ./build
따옴표 요령: 공백/특수문자가 들어갈 수 있는 자리는 템플릿에서 반드시 따옴표로 감싸.
예:--desc "$1",-- bash -lc '$2'등.
환경변수 (Environment Variables)
NOTI_WEBHOOK: Discord Webhook URL (최우선)NOTI_CONFIG:.noti.conf경로 강제NOTI_PRESET:.noti.preset경로 강제NOTI_DEBUG: 디버그 레벨0또는 unset: 끔1: 기본 디버그(시도/HTTP 코드/재시도/백오프, URL 마스킹)2: + 페이로드 앞 200자 프리뷰
예:
export NOTI_DEBUG=1
noti send "debug on"서브커맨드 (Commands)
1) send — 텍스트 메시지
형식
noti send [--allow-mentions] <message...>
``)
**옵션**
- `--allow-mentions` : 멘션 허용(기본 차단: `@everyone`, `@here` 안 울림)
**예시**
```bash
noti send "배포 시작"
noti send --allow-mentions "@here 배포 시작"2) embed — 임베드 카드
형식
noti embed --title <t> --desc <d> [--color <int>] [--field <name> <value>]... [--allow-mentions]설명
--color는 10진수(예: 초록3066993, 빨강15158332, 파랑3447003)--field는 여러 번 사용 가능 (inline=true로 표시)
예시
noti embed --title "Build Result" --desc "✅ Success" --color 3066993 --field Branch main --field Duration "12m 3s"
jq없으면 간소 텍스트로 폴백됨.
3) file — 파일 업로드
형식
noti file <path> [--message <m>] [--allow-mentions]설명
- 멀티파트 업로드(
file1=@<path>). Discord 업로드 제한 정책에 따름. --message로 캡션 텍스트 동봉
예시
printf "line 1
line 2
" > /tmp/demo.log
noti file /tmp/demo.log --message "빌드 로그"4) run — 명령 실행 후 결과 전송
형식
noti run [--title <t>] [--attach-log] [--attach-log-on-fail] [--allow-mentions] -- <cmd> [args...]설명
- 실행 시간/시작/종료/exit code 포함
--attach-log: 항상 실행 로그 첨부--attach-log-on-fail: 실패시에만 로그 첨부jq있으면 임베드 카드로 요약, 없으면 텍스트
예시
# 성공 케이스
noti run --title "Quick success" -- echo "ok"
# 실패 + 실패시에만 로그 첨부
noti run --title "Fail demo" --attach-log-on-fail -- bash -c 'echo "compile..."; echo "ERR" >&2; exit 1'
# 항상 로그 첨부
noti run --title "With log always" --attach-log -- bash -c 'echo "doing work"; sleep 2; echo "done"'반환값
- 내부적으로 실행한 명령의 exit code 를 그대로 반환.
프리셋(별칭) 상세
- 프리셋은 서브커맨드 앞단 프리 디스패치로 처리돼서,
noti <이름> ...형태로 바로 실행 가능. - 템플릿은 이후 재호출되어 실제
send|embed|file|run으로 전달됨. - 인자 바인딩 규칙:
$1,$2, … : 위치 인자${@:3}: 3번째 이후 전부- 공백/특수문자 포함 가능(템플릿에서 꼭 따옴표로 감싸)
- 예약어 이름 금지:
send,embed,file,run,help,-h,--help
고급 예시
# .noti.preset
# 멘션 허용 ping
ping-here = send --allow-mentions "@here [PING] $1"
# 커스텀 색으로 요약
summary = embed --title "$1" --desc "$2" --color 3447003 --field Tag "$3" --field Host "$4"noti ping-here "서버 체크"
noti summary "Backup" "✅ OK" nightly db01디버깅
export NOTI_DEBUG=1 # 기본 디버그
export NOTI_DEBUG=2 # + 페이로드 프리뷰(앞 200자)나오는 로그
- 전송 URL(토큰 마스킹)
- 시도 횟수/HTTP 코드
- 429의
retry_after대기 / 5xx 백오프 - 프리셋 로드 경로/개수
- (레벨2) 페이로드 일부 프리뷰
레이트 리밋/에러 처리
- 204: 성공(Discord Webhook은 본문 없이 204 반환)
- 429:
retry_after만큼 자동 대기 후 재시도 - 5xx: 지수 백오프 재시도
- 그 외: STDERR로 코드/본문 출력 후 실패 반환
보안 모범 사례
- Webhook URL은 절대 코드/레포에 커밋하지 말 것
- CI 비밀변수/환경변수에 저장
- 로컬 히스토리 회피:
HISTCONTROL=ignorespace+ 명령 앞에 공백 - 디버그 레벨 2에서도 URL은 마스킹됨
CI/CD 예시
GitHub Actions
- name: Notify
run: |
echo "ok" > build.log
NOTI_WEBHOOK="${{ secrets.DISCORD_WEBHOOK }}" noti file build.log --message "빌드 로그"GitLab CI
script:
- export NOTI_WEBHOOK="$DISCORD_WEBHOOK"
- noti run --title "CI: test" --attach-log-on-fail -- make testcron
0 9 * * 1-5 NOTI_WEBHOOK=... /usr/local/bin/noti send "Good morning ☕"Makefile
notify:
NOTI_WEBHOOK=$(DISCORD_WEBHOOK) noti send "make notify"문제 해결(Trbleshooting)
NOTI_WEBHOOK 가 비어있음
→export NOTI_WEBHOOK=...또는.noti.conf에webhook = ...추가임베드가 텍스트로만 보임
→jq설치 필요- 공백/따옴표 인자 깨짐
→ 프리셋 템플릿에서 반드시"${1}"식으로 따옴표 사용 알 수 없는 명령 또는 프리셋 없음
→.noti.preset경로/오탈자 확인.NOTI_PRESET로 위치 강제 가능.- 반응 없음
→ 성공 시 204라 터미널 출력이 없음. 채널에서 도착 확인 +NOTI_DEBUG=1로 재시도.
반환값 요약
send|embed|file: HTTP 204이면 0, 그 외 실패 시 1run: 실행한 명령의 exit code 그대로 반환
라이선스 / 크레딧
- 사내/개인 자동화용 스크립트 성격. 별도 라이선스가 필요하면 README 하단에 추가하면 돼.