Redmine + Mattermost 운영 구축 가이드 (네이티브 설치 / 폴링 연동)
목적: Ubuntu 서버에 Mattermost와 Redmine을 Docker 없이 직접(네이티브) 설치하고, 운영 Bitnami Redmine 3.4 데이터를 마이그레이션한 뒤, 미들웨어 폴링 방식으로 양방향 연동(채널 알림 + 슬래시 일감생성 + 담당자/관람자 DM)을 구성한다. 향후 주간/월간 통계의 Mattermost 채널 게시까지 확장한다.
연동 방식 결정 사항 - Redmine→Mattermost 채널 알림: 미들웨어 폴링이 프로젝트별 webhook으로 전송 (간략) · 프로젝트→채널 매핑은 Redmine 프로젝트 커스텀필드 "Mattermost Webhook"에서 읽음 · redmine_messenger 플러그인은 사용 안 함(또는 알림 off) — 형식 통제 위해 미들웨어가 담당 - Redmine→Mattermost DM: 미들웨어 폴링이 담당자/관람자에게 전송 (상세, 이메일 매칭) - Mattermost→Redmine 일감생성: 슬래시
/issue+ 미들웨어 (사이트·담당자 동적 조회) - 통계 게시: 미들웨어 스케줄 작업으로 REST API 집계 후 채널 전송 (향후) - 단일 미들웨어 프로세스(Flask + 폴링 스레드)로 위 전부 처리 — webhook 플러그인 미사용설치 순서: ① Mattermost → ② Redmine → ③ Redmine 플러그인 → ④ 데이터 마이그레이션 → ⑤ 미들웨어 → ⑥ 연동 설정
사전 정보 / 준비물
작업 전 아래를 준비/확인한다.
- [ ] Ubuntu 서버 (24.04 LTS 권장 — 검증 사례 풍부. 26.04는 너무 최신이라 운영 비권장)
- [ ] 서버 고정 IP 확보 (예:
192.168.13.12) — 항상 켜진 서버, WSL 아님 - [ ] 운영 DB 덤프
redmine_backup.sql(utf8mb4로 추출, 약 627MB) - [ ] 운영 첨부파일
attachments_storage폴더 (원본D:\redmine\attachments_storage) - [ ] root 또는 sudo 권한
버전 메모 (작성 시점 기준) - Mattermost 최신: 11.8.x (매월 16일 릴리스). Team Edition(무료) 사용 - Redmine: 6.1.x (Ruby 3.4 기반) — 최신 안정 버전. 폴링 방식이라 webhook 플러그인 제약 없음 - DB: Mattermost는 PostgreSQL 권장 / Redmine은 MySQL 사용 (분리 운영)
왜 6.1인가: 6.0으로 낮추려던 유일한 이유는 webhook 플러그인(Ruby 3.4 의심)이었으나, 실제 원인은 Ruby 버전이 아니라 플러그인 구조 문제였고 → 폴링으로 전환하며 webhook을 배제. 따라서 최신 6.1을 쓴다. 실제 사용하는 CKEditor5·messenger는 6.1(Ruby 3.4)에서 동작 확인됨.
① Mattermost 설치
Mattermost는 PostgreSQL을 권장한다. (Redmine의 MySQL과 별개 DB 엔진)
1-1. PostgreSQL 설치 및 DB 준비
sudo apt update
sudo apt install -y postgresql postgresql-contrib
# DB/계정 생성
sudo -u postgres psql <<'SQL'
CREATE DATABASE mattermost;
CREATE USER mmuser WITH PASSWORD 'mm_strong_password';
GRANT ALL PRIVILEGES ON DATABASE mattermost TO mmuser;
\c mattermost
GRANT ALL ON SCHEMA public TO mmuser;
SQL
1-2. Mattermost 서버 다운로드/설치
# 최신 버전 확인: https://mattermost.com/download/
MM_VERSION="11.8.2"
cd /tmp
wget https://releases.mattermost.com/${MM_VERSION}/mattermost-${MM_VERSION}-linux-amd64.tar.gz
tar -xzf mattermost-${MM_VERSION}-linux-amd64.tar.gz
sudo mv mattermost /opt/
sudo mkdir -p /opt/mattermost/data
# 전용 사용자 생성 및 권한
sudo useradd --system --user-group mattermost
sudo chown -R mattermost:mattermost /opt/mattermost
sudo chmod -R g+w /opt/mattermost
1-3. 설정 (config.json)
sudo -u mattermost nano /opt/mattermost/config/config.json
- SqlSettings.DriverName:
postgres - SqlSettings.DataSource:
postgres://mmuser:mm_strong_password@localhost:5432/mattermost?sslmode=disable&connect_timeout=10 - ServiceSettings.SiteURL:
http://<서버IP>:8065(⚠️ 필수 — 슬래시/웹훅 정상화에 필요)
1-4. systemd 서비스 등록
sudo tee /lib/systemd/system/mattermost.service > /dev/null <<'UNIT'
[Unit]
Description=Mattermost
After=network.target postgresql.service
Requires=postgresql.service
[Service]
Type=notify
ExecStart=/opt/mattermost/bin/mattermost
TimeoutStartSec=3600
Restart=always
RestartSec=10
WorkingDirectory=/opt/mattermost
User=mattermost
Group=mattermost
LimitNOFILE=49152
[Install]
WantedBy=multi-user.target
UNIT
sudo systemctl daemon-reload
sudo systemctl enable --now mattermost
sudo systemctl status mattermost
sudo ss -tlpn | grep 8065 # 리스닝 확인
1-5. 최초 설정
- 브라우저
http://<서버IP>:8065접속 - 첫 가입 계정 = 시스템 관리자 (예: harry@naiz.co.kr)
- 팀 생성:
Naiz - 채널 생성: 알림용 채널 (영문 핸들 권장, 예:
redmine)
중요 — 사내 IP 연동 시 (⑥에서 다시 사용) System Console → Environment → Developer → Allow untrusted internal connections 에 미들웨어/서버 IP 추가. (사설 IP로의 outbound를 막는 SSRF 보호를 풀어야 슬래시 명령이 미들웨어를 호출 가능)
② Redmine 설치 (네이티브)
Ruby 3.4 + Redmine 6.1 + MySQL. Passenger + Apache 또는 Puma. 여기서는 Passenger+Apache.
2-1. 필수 패키지
sudo apt update
sudo apt install -y \
build-essential ruby ruby-dev \
mysql-server libmysqlclient-dev \
imagemagick libmagickwand-dev \
libxml2-dev libxslt1-dev zlib1g-dev libssl-dev \
apache2 libapache2-mod-passenger \
git curl pkg-config \
ghostscript gsfonts
ruby --version # Redmine 6.1은 Ruby 3.4 기반 (3.3 이상)
⚠️ Ruby 버전이 핵심 변수. Redmine 6.1 공식 기준은 Ruby 3.4(3.3 이상 지원). 그런데 Ubuntu 24.04 기본
ruby패키지는 3.2 정도라 낮을 수 있다. 두 가지 선택: 1.ruby --version이 3.3 이상이면 그대로 진행 (6.1이 3.3에서도 대체로 동작) 2. 3.2 이하이거나 3.4를 맞추려면 rbenv로 Ruby 3.4 설치 (아래)rbenv로 Ruby 3.4 설치 (필요 시):
rbenv 사용 시 이후sudo apt install -y rbenv ruby-build # 또는 git clone 방식 rbenv install 3.4.1 rbenv global 3.4.1 ruby --version # 3.4.x 확인bundle/gem/rake가 rbenv Ruby를 쓰도록 PATH/shim 설정 확인. Apache+Passenger의PassengerRuby경로도 rbenv Ruby로 지정해야 함(2-6 참고).ImageMagick은 네이티브 설치라 영구적 (Docker 때처럼 휘발되지 않음 — 장점).
2-2. MySQL DB 준비
sudo mysql_secure_installation # 초기 보안 설정
sudo mysql <<'SQL'
CREATE DATABASE redmine CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'redmine'@'localhost' IDENTIFIED BY 'redmine_strong_password';
GRANT ALL PRIVILEGES ON redmine.* TO 'redmine'@'localhost';
FLUSH PRIVILEGES;
SQL
2-3. Redmine 6.1 다운로드
cd /opt
sudo git clone https://github.com/redmine/redmine.git -b 6.1-stable redmine
# 또는 공식 tarball: https://www.redmine.org/releases/redmine-6.1.x.tar.gz
sudo chown -R www-data:www-data /opt/redmine
cd /opt/redmine
2-4. DB 연결 설정
sudo -u www-data tee config/database.yml > /dev/null <<'YML'
production:
adapter: mysql2
database: redmine
host: localhost
username: redmine
password: "redmine_strong_password"
encoding: utf8mb4
YML
2-5. gem 설치 및 초기화
cd /opt/redmine
sudo gem install bundler
sudo -u www-data bundle install --without development test
# 시크릿 토큰 + DB 스키마 생성 (최초 빈 스키마)
sudo -u www-data bundle exec rake generate_secret_token
sudo -u www-data RAILS_ENV=production bundle exec rake db:migrate
# (마이그레이션 단계 ④에서 운영 데이터로 덮어쓸 것이므로 기본 데이터 로드는 생략 가능)
2-6. Apache + Passenger 연동
sudo tee /etc/apache2/sites-available/redmine.conf > /dev/null <<'CONF'
<VirtualHost *:80>
ServerName redmine.naiz.local
DocumentRoot /opt/redmine/public
<Directory /opt/redmine/public>
Require all granted
Options -MultiViews
</Directory>
PassengerRuby /usr/bin/ruby
</VirtualHost>
CONF
sudo a2ensite redmine
sudo a2enmod passenger
sudo systemctl reload apache2
- 접속 확인:
http://<서버IP>/→ Redmine 화면, 최초admin/admin
포트 충돌 주의: Apache(80)와 Mattermost(8065)는 포트가 다르므로 공존 가능.
⚠️ rbenv로 Ruby 3.4를 설치한 경우:
PassengerRuby를 시스템 ruby가 아니라 rbenv ruby 경로로 지정해야 한다. 확인:rbenv which ruby(예:/home/<user>/.rbenv/versions/3.4.1/bin/ruby). Passenger는 rbenv ruby용 wrapper가 필요할 수 있다 →passenger-config about ruby-command참고. 시스템 ruby(apt)로 3.3 이상을 충족하면/usr/bin/ruby그대로 둬도 됨.
③ Redmine 플러그인 설치
플러그인은
/opt/redmine/plugins/아래에 둔다. 네이티브라 영구 보존(휘발 없음). 설치 순서: CKEditor5 → messenger. (webhook 플러그인은 사용하지 않음 — 폴링으로 대체)
3-1. CKEditor5 (옛 HTML/중첩/base64 이미지 렌더링)
cd /opt/redmine/plugins
sudo git clone https://github.com/litesolutions/redmine_ckeditor5.git redmine_ckeditor5
# base64 인라인 이미지 허용: allowed_protocols에 data 추가
sudo sed -i 's/afs aim callto ed2k feed ftp gopher http https irc mailto news/afs aim callto data ed2k feed ftp gopher http https irc mailto news/' \
redmine_ckeditor5/lib/redmine_ckeditor5.rb
cd /opt/redmine
sudo -u www-data bundle install --without development test
sudo chown -R www-data:www-data plugins/redmine_ckeditor5
sudo systemctl reload apache2
확인:
grep -A5 'def allowed_protocols' plugins/redmine_ckeditor5/lib/redmine_ckeditor5.rb→ 목록에data포함. ImageMagick은 2-1에서 이미 설치됨(CKEditor5 썸네일용).
3-2. redmine_messenger (선택 — 미들웨어가 채널 알림을 담당하므로 생략 가능)
채널 알림은 ⑤ 미들웨어가 프로젝트별 webhook으로 처리한다. 따라서 messenger는 필수가 아니다. 설치하더라도 ⑥ 6-2에서 알림을 꺼서 중복을 막아야 한다. 채널 형식을 미들웨어로 통제할 것이므로 신규 구축에서는 생략을 권장. (참고용으로 설치 절차만 남김)
cd /opt/redmine/plugins
sudo git clone https://github.com/AlphaNodes/redmine_messenger.git
cd /opt/redmine
sudo -u www-data bundle install
sudo -u www-data RAILS_ENV=production bundle exec rake redmine:plugins:migrate
sudo chown -R www-data:www-data plugins/redmine_messenger
sudo systemctl reload apache2
plugins:migrate 후 프로젝트 설정 페이지 오류(
default_mentions) 나면 migrate 재실행.
④ Redmine 데이터 마이그레이션 (운영 3.4 → 6.1)
4-1. 덤프 파일 서버로 복사
# 운영에서 추출한 redmine_backup.sql 을 서버로 전송 (scp/rsync 등)
# 예: /tmp/redmine_backup.sql
ls -lh /tmp/redmine_backup.sql
운영 추출 명령(참고):
mysqldump -u root -p --default-character-set=utf8mb4 bitnami_redmine > redmine_backup.sql(--default-character-set하이픈 2개)
4-2. DB 초기화 후 덤프 복원
⚠️ 2-5에서 빈 스키마를 만들었으므로, 깨끗이 비우고 순수 3.4 덤프만 넣는다.
sudo mysql -e "DROP DATABASE redmine; CREATE DATABASE redmine CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
sudo mysql --default-character-set=utf8mb4 redmine < /tmp/redmine_backup.sql
4-3. 스키마 마이그레이션 (3.4 → 6.1)
cd /opt/redmine
sudo -u www-data RAILS_ENV=production bundle exec rake db:migrate
- 2017~2025 마이그레이션이 순서대로 실행, 에러 없이 끝까지 가면 성공
- 충돌 시(예: 기존 테스트 데이터 혼재) → 4-2의 DROP/CREATE부터 다시
4-4. 첨부파일 복원
# attachments_storage 를 서버로 옮긴 뒤
sudo cp -r /tmp/attachments_storage/* /opt/redmine/files/
sudo chown -R www-data:www-data /opt/redmine/files
연/월 폴더 구조(
2019/05/...)가files/바로 아래에 오도록.
4-5. 텍스트 형식 설정 + 검증
sudo systemctl reload apache2
- 접속 → 운영 계정으로 로그인(admin/admin 아님)
- 관리 → 설정 → 일반 → 본문 형식 = CKEditor (3-1 플러그인 덕분에 옛 HTML·base64 정상 렌더)
- 프로젝트/일감/위키/한글/첨부 확인
- ⚠️ API 키 확인: 관리 → 내 계정 → API 액세스 키 (마이그레이션 후 키가 바뀜 → ⑤⑥에서 사용)
⑤ 미들웨어 설치 (통합: 슬래시 + 폴링 + 채널 + DM)
단일 Flask 프로세스가 모든 연동을 담당 (포트 충돌 없음, systemd 하나로 관리): - (a) 슬래시
/issue수신 → 다이얼로그 → 일감 생성 (사이트·담당자 동적 조회) - (b) 폴링: 변경 일감 감지 → ① 채널 알림(간략) + ② 담당자·관람자 DM(상세) - (c) 채널 매핑: Redmine 프로젝트 커스텀필드에서 webhook URL을 읽음 (코드에 매핑 없음) - (d) 통계: 주간/월간 집계 → 채널 게시 (향후, 같은 프로세스에 APScheduler로 추가)검증 완료 사항(테스트 환경): 슬래시 일감생성, 폴링 DM, 프로젝트별 채널 전송, 동적 사이트/담당자 목록, 신규/수정 구분, 외부 링크(localhost 문제 해결) — 모두 동작 확인.
5-1. Python 환경
sudo apt install -y python3 python3-pip python3-venv
sudo mkdir -p /opt/redmine-middleware
cd /opt/redmine-middleware
python3 -m venv venv
./venv/bin/pip install flask requests apscheduler
apscheduler= 통계 스케줄링용(향후). 폴링 자체는 내장 스레드로 동작(추가 패키지 불필요).
5-2. 미들웨어 파일 배치
검증된 미들웨어(middleware_final.py)를 /opt/redmine-middleware/middleware.py로 배치하고
상단 CONFIG를 운영 환경에 맞게 채운다. (테스트의 ~/redmine60-test/middleware_final.py 내용)
5-3. CONFIG 주요 항목
[접속]
REDMINE_URL = http://localhost:3000 # 미들웨어→Redmine API(내부)
REDMINE_LINK_URL = http://<서버IP>:3000 # 메시지에 넣을 외부 링크 (localhost 문제 방지)
REDMINE_API_KEY = <④에서 확인한 새 API 키>
MATTERMOST_URL = http://<서버IP>:8065
MM_SLASH_TOKEN = <슬래시 명령 토큰>
MM_BOT_TOKEN = <봇 토큰>
BOT_USER_ID = <봇 user_id : GET /api/v4/users/me>
SUBMIT_URL = http://<서버IP>:5000/submit
[일감 생성 기본값]
PROJECT_ID = issue # 슬래시 일감생성 대상 프로젝트
TRACKER_ID = <요구사항 트래커 ID>
SITE_FIELD_NAME = "지원 사이트" # 사이트 커스텀필드 이름(자동 탐색)
WEBHOOK_FIELD_NAME= "Mattermost Webhook" # 프로젝트 채널 webhook 커스텀필드 이름
[폴링/캐시]
POLL_INTERVAL = 60 # 초
DRY_RUN = False # 검증 시 True(실제 전송 안 함)
STATE_FILE = 폴링 상태(중복방지) 저장 경로
CACHE_TTL = 300 # 사이트/사용자/webhook 조회 캐시(초)
[메시지 문구 — 플레이스홀더 자유 편집]
MSG_CHANNEL = 채널용 간략 메시지 (예: "[{event}] #{issue_id} {subject} ({status}) - {assignee}\n{link}")
MSG_DM = DM용 상세 메시지 (프로젝트/상태/우선순위/담당자/등록자/사이트/링크)
EVENT_NEW = "신규" EVENT_UPDATE = "변경"
사이트·담당자 목록은 하드코딩하지 않음 — Redmine에서 동적 조회(캐시). 사이트 = "지원 사이트" 커스텀필드 possible_values, 담당자 = 활성 사용자 목록.
5-4. 프로젝트별 채널 매핑 (Redmine에서 관리)
코드에 매핑을 두지 않고, Redmine 프로젝트 커스텀필드에 webhook URL을 저장한다.
① 프로젝트 커스텀필드 생성
- 관리 → 사용자 정의 항목 → 프로젝트 탭 → 새 필드
- 형식: 텍스트, 이름: Mattermost Webhook, 모든 프로젝트에 적용
② Mattermost에서 채널별 Incoming Webhook 생성 - 팀 → Integrations → Incoming Webhooks → Add → 채널 선택 → URL 발급 - 프로젝트(채널)마다 반복
③ 각 Redmine 프로젝트에 URL 입력 - 프로젝트 → 설정 → 정보 → "Mattermost Webhook" 필드에 해당 채널 webhook URL - 비워두면 그 프로젝트는 채널 알림 안 감 (자연스러운 on/off)
미들웨어는 일감의 프로젝트를
GET /projects/{id}.json?include=custom_fields로 조회해 이 필드 값(webhook URL)을 읽어 전송한다. 프로젝트 추가/변경 시 코드 수정 불필요.
5-5. 미들웨어 동작 요약
슬래시 /issue:
사이트(지원사이트 커스텀필드 possible_values) + 담당자(활성 사용자) 동적 조회 → 다이얼로그
제출 → POST /issues.json (사이트 커스텀필드 + 담당자 지정)
폴링 (POLL_INTERVAL초, 백그라운드 스레드):
GET /issues.json?updated_on>=<last_check>&include=watchers&status_id=*&sort=updated_on:desc
각 변경 일감 (중복방지: (issue_id, updated_on) 기록):
· 신규/수정 판단 (created_on == updated_on → 신규)
· 채널: 프로젝트 webhook URL로 간략 메시지 (담당자 없어도)
· DM: 담당자+관람자 → 이메일 매칭 → 상세 메시지 (있을 때만)
sudo tee /lib/systemd/system/redmine-middleware.service > /dev/null <<'UNIT'
[Unit]
Description=Redmine-Mattermost Middleware
After=network.target
[Service]
Type=simple
WorkingDirectory=/opt/redmine-middleware
ExecStart=/opt/redmine-middleware/venv/bin/python /opt/redmine-middleware/middleware.py
Restart=always
RestartSec=5
User=www-data
Group=www-data
[Install]
WantedBy=multi-user.target
UNIT
sudo systemctl daemon-reload
sudo systemctl enable --now redmine-middleware
sudo systemctl status redmine-middleware
네이티브라 미들웨어도 systemd로 상시 구동 + 자동 재시작. (폴링 루프가 안에서 동작)
⑥ Mattermost ↔ Redmine 연동 설정
6-1. Mattermost 측
- 봇 계정 생성 → 토큰 확보 (System Console → Integrations → Bot Accounts)
- 봇을 팀
Naiz에 추가 - 채널별 Incoming Webhook 생성 → 각 프로젝트 채널마다 URL 발급 (⑤ 5-4에서 사용)
- 슬래시 명령
/issue등록 → Request URL =http://<서버IP>:5000/issue, 토큰 확보 - Allow untrusted internal connections 에 서버 IP 추가 (System Console → Environment → Developer)
- Site URL 확인 (1-3에서 설정) — 변경 시 Mattermost 재시작
6-2. 채널 알림 — 미들웨어가 담당 (redmine_messenger 비활성화)
⚠️ redmine_messenger 플러그인은 채널 알림을 보내지 않도록 끈다. 채널 알림은 ⑤ 미들웨어가 프로젝트별 webhook으로 전송하므로, 플러그인을 켜두면 중복 알림이 발생.
- 관리 → 설정 → 플러그인 → Redmine Messenger → post 옵션(생성/수정 알림) 끄기
- 또는 프로젝트별 Messenger 채널 설정을 비움
- (플러그인 자체는 설치돼 있어도 됨 — 알림만 끄면 됨. 완전히 제거해도 무방)
messenger를 굳이 쓰지 않을 거면 ③ 단계에서 설치를 생략해도 된다. 단, 채널 알림 형식을 자유롭게 제어하려면 미들웨어 방식(현재 구성)이 유리.
6-3. 프로젝트별 채널 webhook 설정 (⑤ 5-4 재확인)
- Redmine 프로젝트 커스텀필드
Mattermost Webhook에 각 프로젝트 채널의 webhook URL 입력 - 비운 프로젝트는 채널 알림 미발송
6-4. 호스트 이름 (Redmine 자체 링크 정상화)
- 관리 → 설정 → 일반 → 호스트 이름과 경로:
<서버IP>(또는 도메인) - 프로토콜: HTTP (또는 HTTPS 구성 시)
- (미들웨어 메시지 링크는
REDMINE_LINK_URL로 별도 제어되지만, Redmine 자체 메일/링크 위해 설정)
6-5. 미들웨어 설정값 채우기 + 재시작
- ⑤의 CONFIG에 실제 토큰/키/ID 입력 (특히 마이그레이션 후 바뀐 API 키)
sudo systemctl restart redmine-middleware
6-6. 검증 체크리스트
- [ ] 일감 생성/수정 → 1분 내 프로젝트 채널에 간략 알림 (미들웨어 폴링, 담당자 없어도)
- [ ] 일감 생성/수정 → 1분 내 담당자·관람자에게 상세 DM (폴링)
- [ ] 신규/수정이 메시지에 구분 표시 ("신규"/"변경")
- [ ] 메시지 링크가 외부 주소(
REDMINE_LINK_URL)로 정상 — localhost 아님 - [ ] Mattermost
/issue→ 다이얼로그(사이트·담당자 동적) → Redmine 일감 생성 - [ ] redmine_messenger 알림이 꺼져 중복 없음
- [ ] (향후) 주간/월간 통계가 지정 채널에 게시
부록 A — 운영 전환 시 주의 (Docker 테스트 → 네이티브 운영)
테스트(Docker)에서 검증한 것과 운영(네이티브)의 차이/이점:
| 항목 | Docker 테스트 | 네이티브 운영 |
|---|---|---|
| ImageMagick | 컨테이너 재생성 시 휘발 | apt 설치로 영구 ✓ |
| CKEditor5 data 패치 | 컨테이너 내 수정 휘발 위험 | 파일로 영구 보존 ✓ |
| 플러그인 | 볼륨 마운트 필요 | /opt/redmine/plugins에 영구 ✓ |
| webhook 플러그인 | Zeitwerk/init 경로 문제로 실패 | 사용 안 함 (폴링 대체) |
| IP | WSL IP 가변, 포트포워딩 필요 | 고정 IP, 포워딩 불필요 ✓ |
| 프로세스 관리 | docker compose | systemd (자동 재시작) ✓ |
부록 B — 트러블슈팅 (테스트에서 실제로 겪은 것들)
| 증상 | 원인 | 해결 |
|---|---|---|
복원 시 Table 'reactions' already exists |
기존 데이터와 덤프 혼재 | DB DROP/CREATE 후 순수 덤프 복원 |
본문 <p> 태그가 글자로 |
본문형식 "없음" | CommonMark → 최종 CKEditor |
| 중첩 HTML이 코드블록으로 | CommonMark 한계 | CKEditor5 설치 |
| base64 이미지 빈 박스 | sanitizer가 data: 차단 | allowed_protocols에 data 추가 |
| 프로젝트 설정 Internal error | 플러그인 migrate 누락 | rake redmine:plugins:migrate |
| API 빈 응답/거부 | REST API 비활성 또는 키 변경 | 설정→API 활성화 / 새 키 확인 |
| 슬래시→미들웨어 실패 | 사설 IP SSRF 차단 | 내부연결 허용에 서버 IP 추가 |
| 폴링 실행 시 포트 충돌(Address already in use) | 미들웨어와 별도 프로세스로 5000 중복 | 폴링을 미들웨어에 스레드로 통합(현재 구성) |
| 메시지 링크가 localhost로 열림 | REDMINE_URL을 링크에 그대로 사용 | REDMINE_LINK_URL(외부주소) 분리 |
| DM이 안 옴 | 폴링 미실행 / 첫실행 기준점 이후로 안 잡힘 | 미들웨어(폴링 내장) 상시 구동 / 일감 재수정으로 updated_on 갱신 |
| 채널 알림 중복 | messenger + 미들웨어 동시 전송 | messenger 알림 끄기(6-2) |
| 특정 프로젝트만 webhook 동작 | 프로젝트 커스텀필드 webhook URL 미입력 | 해당 프로젝트에 URL 입력(빈값=미발송) |
| Mattermost 메시지 시각 오차 | MM 서버 PC 시계 오차 | 해당 PC NTP 시간 동기화 |
부록 C — 왜 webhook이 아니라 폴링인가 (의사결정 기록)
테스트에서 redmine_webhook_plugin을 시도했으나 다음 순서로 막힘:
1. Zeitwerk 규칙 위반 — jobs/services 폴더의 클래스 경로 불일치로 Redmine 부팅 실패
2. 폴더 이동으로 부팅은 해결했으나, init.rb가 옛 경로를 require_dependency 하여
to_prepare 블록 중단 → issue_patch 미등록 → 일감 이벤트 자체를 감지 못 함
3. Database Runner 모드는 전송을 큐(Pending)에 쌓고 rake redmine:webhooks:process를
주기 실행해야 발송 → 결국 cron 주기 실행 = 폴링과 동일 구조
4. Ruby 3.4(6.1)/3.3(6.0) 양쪽에서 동일 문제 → Ruby 버전 무관, 플러그인 구조 문제
결론: webhook의 "즉시성" 이점이 이 플러그인 구조에선 살지 않고 불안정. 미들웨어 폴링 채택: - Redmine을 건드리지 않아 안정적 (플러그인 부팅 리스크 없음) - 검증된 이메일매칭/DM 로직 재사용, 채널/DM/슬래시/통계를 한 프로세스로 통합 - 1분 주기 지연은 실무상 무의미
부록 D — 주요 설정값 모음 (채워서 사용)
[서버] Ubuntu 24.04 LTS, 고정 IP <서버IP>
[Mattermost] /opt/mattermost, :8065, PostgreSQL(mattermost/mmuser)
팀 Naiz, 봇 토큰, 봇 user_id, 채널별 Incoming Webhook, 슬래시 /issue 토큰, Site URL
[Redmine] /opt/redmine, Apache+Passenger :80, MySQL(redmine/redmine)
Ruby 3.4(또는 3.3+), Redmine 6.1, API 키(마이그레이션 후 재확인)
대상 프로젝트 식별자 issue, 트래커 ID
커스텀필드: "지원 사이트"(일감), "Mattermost Webhook"(프로젝트)
[플러그인] redmine_ckeditor5(+data 패치) [필수]
redmine_messenger [선택 — 미설치/알림off 권장, 채널은 미들웨어가 담당]
[미들웨어] /opt/redmine-middleware, :5000, systemd, 폴링 60초 (단일 프로세스)
슬래시 /issue + 폴링(채널 간략 + DM 상세) + 통계(향후)
채널 매핑 = Redmine 프로젝트 커스텀필드에서 동적 조회 (코드 매핑 없음)
사이트/담당자 목록 = Redmine에서 동적 조회
REDMINE_LINK_URL로 메시지 링크 외부주소화
[연동 흐름 요약]
Redmine→MM 채널 : 폴링 감지 → 프로젝트 webhook으로 간략 메시지 (담당자 무관)
Redmine→MM DM : 폴링 감지 → 담당자/관람자 이메일매칭 → 상세 메시지
MM→Redmine : /issue 슬래시 → 다이얼로그(사이트/담당자 동적) → 일감 생성
부록 E — 보안/마무리 체크
- [ ] 노출된 인증정보 전부 재발급: Redmine API 키, MM 봇 토큰, MM 슬래시 토큰, Incoming Webhook URL
- [ ] 미들웨어 CONFIG의 키/토큰을 환경변수 또는 별도 파일로 분리(코드에 평문 지양)
- [ ] HTTPS 적용 시: Nginx/Apache 리버스 프록시 + 인증서, Site URL/호스트이름 https로
- [ ] 정기 백업: Redmine MySQL 덤프 + /opt/redmine/files, Mattermost DB + /opt/mattermost/data
- [ ] 방화벽: 필요한 포트만 (80/443, 8065 또는 프록시 뒤로)