여백의 미: 1Panel로 Yohaku/Shiroi 테마 완벽 배포 가이드
Note이것은 'Mix Space + Yohaku 배포 시리즈'의 두 번째 글로, 프론트엔드 테마 Yohaku 설치에 초점을 맞춥니다. 아직 백엔드를 배포하지 않으셨다면, 먼저 첫 번째 글을 읽어주세요 — 『처음부터 시작하기 · 1Panel로 Mix Space 백엔드 배포하기』
Yohaku는 일본어 '여백(余白)'에서 따온 이름으로, 그림 속 의도적으로 비워둔 공간이 오히려 채워진 부분보다 더 큰 울림을 주는 것을 의미합니다.
Mix Space 생태계의 최신 프론트엔드 테마입니다. 그 이야기는 오픈소스 Shiro에서 시작하여, 클로즈드 소스 후원 버전 Shiroi를 거쳐 오늘날의 Yohaku에 이르기까지 3대에 걸쳐 이어져 왔으며, 디자인 언어와 구현 방식은 매 단계마다 조용히 진화해 왔습니다. 사이트 전체가 글쓰기를 은유하며, 페이지는 마치 천천히 펼쳐지는 편지지 같습니다. 절제된 색감과 숨결 같은 애니메이션으로 독서 행위 자체를 주인공으로 만듭니다.
세 테마의 관계를 간단히 정리하면:
- Shiro → Mix Space의 최초 오픈소스 프론트엔드 테마, GitHub에 코드 공개
- Shiroi → Shiro의 클로즈드 소스 기부 버전, Shiro 기반으로 발전, 후원 시 접근 가능
- Yohaku(여백) → Shiro / Shiroi에서 더욱 발전한 완전히 새로운 디자인, 동일하게 클로즈드 소스 후원 형태로 유지 관리, 현재 가장 최신 세대
이 글에서 배포할 주인공은 Yohaku입니다. 클로즈드 소스 테마이기 때문에 Docker 이미지를 직접 빌드해야 하며, 아래에서 차근차근 안내해 드리겠습니다. (본 튜토리얼은 Shiroi와도 호환됩니다)
전제 조건: 이 글은 여러분이 클로즈드 소스 버전 저장소에 접근 권한이 있고, Mix Space 백엔드 배포를 완료했으며, 다음 두 가지 주소를 준비했다고 가정합니다:
백엔드 API 주소: 예시https://당신의도메인/api/v3 (백엔드 버전 V13 이전은 v2)백엔드 게이트웨이 주소: 예시https://당신의도메인
아직 백엔드를 배포하지 않으셨다면, 먼저 첫 번째 글을 읽고 완료해주세요.
1단계 · Docker 이미지 빌드
(오픈소스 버전 Shiro를 사용하는 분은 곧바로 2단계로 이동하세요) 제작자가 Shiroi / Yohaku 테마의 사전 빌드된 Docker 이미지를 제공하지 않기 때문에, 우리가 직접 빌드해야 합니다. 하지만 저사양 서버에서 직접 빌드하지 마세요 — 서버 메모리가 터져버릴 수 있습니다 💥
우리의 해결책은 GitHub Actions를 이용해 클라우드에서 빌드를 완료한 후, 이미지를 GitHub Packages(ghcr.io) 비공개 저장소로 푸시하는 것입니다. 서버는 완성된 이미지를 가져오기만 하면 되므로 매우 간편합니다.
1.1 저장소 준비
다음 저장소에 접속하여 우측 상단의 Fork를 클릭하세요:
그런 다음 아래 링크에 접속하여 새 저장소를 만드세요(Choose visibility는 반드시 Private으로 선택!!!), 이름은 yohaku를 권장하며, 다를 경우 이후 워크플로우 파일에서 그에 맞게 수정해야 합니다.
1.2 GitHub 클래식 액세스 토큰(Classic Token) 신청
Actions가 Shiroi 비공개 저장소를 읽고, 빌드된 이미지를 GitHub Packages에 푸시하려면 권한이 필요합니다. 클래식 토큰(Classic Token)을 미리 준비해야 합니다.
https://github.com/settings/tokens/new 에 접속하세요
설명(예: yohaku-build)을 입력하고, 유효 기간은 무기한으로 설정한 후, 다음 권한을 선택하세요:
| 권한 | 용도 설명 |
|---|---|
repo | 비공개 저장소 접근을 포함한 저장소 읽기/쓰기 |
workflow | GitHub Action 워크플로우 업데이트 |
write:packages | GitHub Packages에 이미지 푸시 |
read:packages | GitHub Packages에서 이미지 가져오기 |
Generate token을 클릭하고, 생성된 토큰을 즉시 복사하여 잘 보관하세요 — 이 순간에만 표시됩니다!
1.3 저장소 Actions 변수 설정
Fork한 저장소로 이동하여 순서대로 클릭하세요:
Settings → Secrets and variables → Actions → Repository secrets
다음 두 변수를 추가하세요:
| 변수명 | 입력 내용 |
|---|---|
BASE_URL | Core 백엔드에 연결된 도메인 (예: https://jiye.funcun.top) |
GH_PAT | 이전 단계에서 신청한 Personal Access Token |
NEXT_PUBLIC_GATEWAY_URL | 선택 사항, 예: https://jiye.funcun.top |
NEXT_PUBLIC_API_URL | 선택 사항, 예: https://jiye.funcun.top/api/v3 |
새로 만든 Yohaku 저장소로 이동하여, 방금과 동일하게 변수를 추가하세요
| 변수명 | 입력 내용 |
|---|---|
UPSTREAM_REPO_SECRET | 이전 단계에서 신청한 Personal Access Token |
1.4 빌드 활성화 및 트리거
먼저 새로 만든 Yohaku 저장소로 이동하여, Fork 저장소에 있는 upstream-sync.yml 파일을 해당 저장소에 업로드합니다. 그런 다음 서버/기타 기기에서 다음 스크립트를 실행하고, 안내에 따라 입력하세요 (기본 옵션이 있으면 기본값 권장)
#!/bin/bash
set -euo pipefail
# =============================================
# 스크립트 소개:
# 본 스크립트는 업스트림 저장소의 지정 브랜치를,
# 개인 저장소의 대상 브랜치로 강제 동기화하는 데 사용됩니다.
# 공개/비공개 업스트림 저장소에서 업데이트를 가져와
# 자신의 브랜치로 푸시(미러 동기화 등)하는 데 적합합니다.
# 강제 푸시는 대상 브랜치의 이력을 덮어쓰므로 신중하게 작업하세요!
# =============================================
echo "======================================="
echo " 업스트림 저장소 → 개인 저장소 강제 동기화 도구"
echo "======================================="
echo ""
echo "본 스크립트는 다음 작업을 수행합니다:"
echo "1. 업스트림 저장소를 임시 디렉토리에 클론"
echo "2. 개인 원격 저장소 추가"
echo "3. 업스트림 브랜치를 개인 저장소의 대상 브랜치로 강제 푸시"
echo "주의: 대상 브랜치의 기존 내용은 완전히 덮어쓰기 됩니다!"
echo "======================================="
echo ""
# ---------- 개인 저장소 정보 수집 ----------
read -r -p "GitHub 사용자 이름을 입력하세요: " USERNAME
read -r -p "대상 저장소 이름을 입력하세요: " REPO
echo "GitHub Personal Access Token을 입력하세요 (입력 시 표시되지 않으며, repo 권한 필요):"
read -r -s TOKEN
echo # 줄바꿈
# ---------- 업스트림 저장소 정보 수집 ----------
read -r -p "업스트림 저장소가 비공개인가요? (y/n, 기본값 n): " UPSTREAM_PRIVATE
UPSTREAM_PRIVATE=${UPSTREAM_PRIVATE:-n}
read -r -p "업스트림 저장소의 전체 주소를 입력하세요 (예: https://github.com/innei-dev/Yohaku.git): " UPSTREAM
if [ "$UPSTREAM_PRIVATE" = "y" ] || [ "$UPSTREAM_PRIVATE" = "Y" ]; then
echo "업스트림 저장소가 비공개이므로, 해당 저장소에 접근 가능한 Token이 필요합니다 (입력 시 표시되지 않음):"
read -r -s UPSTREAM_TOKEN
echo
# 인증 정보가 포함된 업스트림 URL 생성
UPSTREAM_AUTH_URL=$(echo "$UPSTREAM" | sed "s|https://|https://x-access-token:${UPSTREAM_TOKEN}@|")
else
UPSTREAM_AUTH_URL="$UPSTREAM"
fi
read -r -p "임시 디렉토리 이름을 입력하세요 (기본값 temp-upstream): " TEMP_DIR
TEMP_DIR=${TEMP_DIR:-temp-upstream}
read -r -p "업스트림 저장소의 브랜치 이름을 입력하세요 (기본값 main): " SRC_BRANCH
SRC_BRANCH=${SRC_BRANCH:-main}
read -r -p "개인 저장소에 푸시할 대상 브랜치 이름을 입력하세요 (기본값 sync): " DST_BRANCH
DST_BRANCH=${DST_BRANCH:-sync}
# ---------- 개인 원격 주소 생성 ----------
MY_REMOTE="https://${USERNAME}:${TOKEN}@github.com/${USERNAME}/${REPO}.git"
# ---------- 임시 디렉토리 처리 ----------
while [ -d "$TEMP_DIR" ] && [ "$(ls -A "$TEMP_DIR" 2>/dev/null)" ]; do
echo ""
echo "경고: '$TEMP_DIR' 디렉토리가 이미 존재하며 비어 있지 않습니다."
read -r -p "삭제하고 다시 생성할까요? (y/n): " answer
if [ "$answer" = "y" ] || [ "$answer" = "Y" ]; then
rm -rf "$TEMP_DIR"
echo "이전 디렉토리를 삭제했습니다."
else
read -r -p "새로운 임시 디렉토리 이름을 입력하세요: " TEMP_DIR
fi
done
# ---------- 동기화 실행 ----------
echo ""
echo "업스트림 저장소 $UPSTREAM 를 $TEMP_DIR 로 클론 중..."
git clone "$UPSTREAM_AUTH_URL" "$TEMP_DIR"
cd "$TEMP_DIR"
echo "개인 원격 저장소 myrepo 추가 중..."
git remote add myrepo "$MY_REMOTE"
echo "$SRC_BRANCH -> myrepo/$DST_BRANCH 강제 푸시 중..."
git push --force myrepo "$SRC_BRANCH:$DST_BRANCH"
cd ..
echo "임시 디렉토리 $TEMP_DIR 정리 중..."
rm -rf "$TEMP_DIR"
echo ""
echo "======================================="
echo "동기화 완료!"
echo "$UPSTREAM 의 $SRC_BRANCH 브랜치를"
echo "$USERNAME/$REPO 의 $DST_BRANCH 브랜치로 강제 푸시했습니다."
echo "======================================="
완료 후 Fork 저장소의 Actions 탭으로 이동하여, 안내 메시지가 나타나면 클릭하여 활성화합니다. 그런 다음 빌드 Workflow를 찾아 우측의 Run workflow를 클릭하여 수동으로 한 번 트리거합니다.
빌드 과정은 보통 5 ~ 10분 정도 소요됩니다. 차 한 잔 하며 기다리세요 ☕
빌드가 완료되면, 저장소 사이드바의 Packages에서 여러분의 이미지를 확인할 수 있습니다. 주소 형식은 다음과 같습니다:
ghcr.io/당신의사용자이름(모두소문자)/yohaku:latest
1.5 1Panel에서 ghcr.io 비공개 저장소 설정
이미지가 비공개 GitHub Container Registry에 저장되어 있으므로, 1Panel이 이미지를 가져오려면 먼저 로그인 인증이 필요합니다.
1Panel 패널에 로그인하여 컨테이너 → 저장소 → 저장소 생성으로 이동하여 다음을 입력하세요:
| 필드 | 내용 |
|---|---|
| 이름 | ghcr.io (알아보기 쉬운 임의의 이름) |
| 저장소 주소 | ghcr.io |
| 사용자 이름 | 당신의 GitHub 사용자 이름 |
| 비밀번호 | 당신의 Personal Access Token (GH_PAT) |
저장 후, 1Panel이 자동으로 연결을 확인합니다. 이제 비공개 이미지를 문제없이 가져올 수 있습니다 🔐
2단계 · 1Panel을 통해 Yohaku 설치
2.1 앱 패키지 업로드
1Panel 패널에 로그인하여 왼쪽 메뉴에서 호스트 → 파일로 이동한 후, 다음 경로로 이동합니다:
/opt/1panel/resource/apps/local
업로드를 클릭하고, Fork한 저장소에서 다운로드한 yohaku.zip 파일을 선택합니다.
2.2 압축 해제, 경로에 주의하세요!
업로드가 완료되면 yohaku.zip을 클릭하고 압축 해제를 선택합니다.
이 단계는 초보자가 가장 자주 실수하는 부분이므로 반드시 주의하세요!
압축을 풀 때 대상 경로를 수동으로 다음과 같이 완성해야 합니다:
/opt/1panel/resource/apps/local/yohaku
경로가 올바르지 않으면 잘못된 디렉토리에 파일이 생성될 수 있으며, 앱 스토어에서 이 로컬 앱을 인식하지 못합니다.
2.3 로컬 앱 동기화
1Panel 앱 스토어로 이동하여 우측 상단의 로컬 앱 동기화 버튼을 클릭합니다. 잠시 후 검색창에 yohaku를 입력하면 방금 추가한 앱을 볼 수 있습니다.
설치를 클릭하여 설정 페이지로 들어갑니다.
2.4 설치 설정 항목 입력
설치 페이지에는 네 개의 필수 입력 항목(및 하나의 읽기 전용 설명)이 있으며, 위에서 아래로 순서대로 입력합니다:
🐳 이미지 주소 (Image Address)
여기에 컨테이너 이미지를 입력합니다. 배포하는 버전에 따라 선택하세요:
오픈소스 버전 Shiro (공식 사전 빌드 이미지 직접 사용):
innei/shiro:latest
클로즈드 소스 버전 Shiroi / Yohaku (1단계에서 직접 빌드한 비공개 이미지 입력):
ghcr.io/당신의사용자이름(모두소문자)/shiroi:latest
📡 공개 API 주소 (PUBLICAPIURL)
Mix Space 백엔드 API 주소를 입력합니다:
https://당신의백엔드도메인/api/v2
끝의 /api/v2 경로를 유지해야 합니다.
🌐 공개 게이트웨이 주소 (PUBLICGATEWAYURL)
Mix Space 백엔드 게이트웨이 주소(즉, 백엔드 루트 도메인)를 입력합니다:
https://당신의백엔드도메인
경로 접미사를 추가할 필요가 없습니다.
🔗 API URL 및 클라이언트 API 주소
API_URL과 NEXT_PUBLIC_CLIENT_API_URL 두 항목의 값은 공개 API 주소와 동일하게 유지하며, 동일한 내용을 입력하면 됩니다:
https://당신의백엔드도메인/api/v2
2.5 설치 시작 🎉
설정이 올바른지 확인하고 설치 시작을 클릭합니다.
1Panel이 자동으로 이미지를 가져와 컨테이너를 시작하며, 네트워크 상태에 따라 몇 분 정도 소요될 수 있습니다. 상태가 실행 중(Running)으로 표시되면 Yohaku가 정식으로 오픈됩니다!
3단계 · 리버스 프록시 및 HTTPS 설정
이전 블로그 글을 참조하세요. 이미 설정했다면 건너뛰어도 됩니다.
부록: Yohaku가 지원하는 확장 Markdown 문법
Shiro 체계를 계승한 Yohaku는 풍부한 확장 Markdown 문법을 지원하여, 여러분의 블로그 글이 단순한 텍스트 이상이 되도록 합니다. 다음은 글을 쓸 때 사용할 수 있는 특색 있는 문법들입니다 —
수학 공식 (KaTeX)
인라인 공식:
질량-에너지 등가 공식 $E = mc^2$ 은 우주에 대한 인류의 인식을 바꾸어 놓았습니다.
블록 공식:
$$
\int_{-\infty}^{+\infty} e^{-x^2} dx = \sqrt{\pi}
$$
알림 배너 (Notice / Banner)
::: warning
여기는 경고 내용입니다. 배경이 눈에 띄는 색상으로 표시됩니다.
:::
::: banner {note}
여기는 비고입니다. 추가 설명을 덧붙이기에 적합합니다.
:::
::: banner {error}
여기는 오류 알림입니다. 위험한 작업을 강조하는 데 사용됩니다.
:::
GFM Alert 문법
> [!NOTE]
> 이것은 비고 사항으로, 독자에게 특정 사항을 상기시킵니다.
> [!IMPORTANT]
> 이것은 중요한 정보로, 간과할 수 없습니다.
> [!WARNING]
> 이것은 경고로, 잠재적인 위험과 관련될 수 있습니다.
스포일러 가리개 (Spoiler)
이 영화의 결말은 ||사실 주인공은 이미 죽어 있었다||는 것으로, 매우 놀랍습니다.
참고: 이것은 취소선 ~~텍스트~~와 효과가 다릅니다. Spoiler는 마우스를 올리기 전까지 내용을 가려서 숨깁니다.
리치 링크 (Rich Link)
단독 행으로 된 링크에 대해, Yohaku는 자동으로 커버 이미지와 요약이 포함된 카드 스타일로 렌더링합니다:
https://github.com/Innei/Yohaku
인식을 지원하는 플랫폼으로는 GitHub 저장소, Commit, Issue, Gist, 그리고 YouTube, Twitter 등이 있습니다.
인라인 링크 아이콘
인라인 링크에는 자동으로 해당 웹사이트의 Favicon이 첨부됩니다:
[Innei의 홈페이지](https://innei.in)를 방문하여 더 알아보세요.
멘션 (Mention)
[Innei]{GH@Innei} 님이 이렇게 아름다운 테마를 만들어 주셔서 감사합니다.
GH@사용자이름은 자동으로 아바타가 포함된 GitHub 사용자 카드로 렌더링됩니다.
접기 블록 (Collapse)
<details>
<summary>클릭하여 상세 내용 펼쳐보기</summary>
여기에 접혀 있던 상세 설명이 있습니다...
</details>
자주 묻는 질문
Q: 이미지 가져오기 속도가 극도로 느리거나 실패하면 어떻게 하나요?
국내 서버에서 ghcr.io를 가져올 때 속도가 느릴 수 있습니다. GitHub Actions의 빌드 Workflow에 Alibaba Cloud ACR로 동기화 푸시하는 단계를 추가한 후, Alibaba Cloud에서 가져오면 됩니다. 구체적인 설정은 박하의 오두막 튜토리얼을 참고하세요.
Q: Actions 빌드가 실패했습니다. 어떻게 해결하나요?
저장소의 Actions 페이지로 이동하여 실패한 Workflow를 클릭해 상세 로그를 확인하세요. 흔한 원인은 다음과 같습니다:
GH_PAT권한 부족,repo와write:packages를 선택했는지 확인DOCKER_NAMESPACE가 모두 소문자가 아님- 저장소가 공개로 설정되어 권한 충돌 발생
Q: 압축 해제 후 앱 스토어에서 yohaku를 찾을 수 없나요?
대부분 압축 해제 경로가 잘못된 경우입니다. 압축 해제 대상이 /opt/1panel/resource/apps/local/yohaku인지 확인하세요 (/opt/1panel/resource/apps/local이 아닙니다). 확인 후 다시 '로컬 앱 동기화'를 클릭하세요.
Q: 페이지에 접속할 수 있지만 백엔드 연결 실패 메시지가 나타나나요?
다음 사항을 확인하세요:
- 네 개의 API 주소 설정 항목이 올바르게 입력되었는지, 특히
/api/v2경로가 누락되지 않았는지 확인 - Mix Space 백엔드의
ALLOWED_ORIGINS에 Yohaku 프론트엔드의 도메인이 포함되어 있는지 확인 - 백엔드 리버스 프록시 및 HTTPS 인증서가 정상 작동하는지 확인
Q: error.api_fetchError Not found 오류가 발생하나요?
이것은 소스 코드의 버그입니다. 메모를 하나 발행하면 해결됩니다...
참고 자료
이 글을 작성하는 과정에서 다음 자료들을 참고했습니다. 블로거 여러분의 아낌없는 공유에 감사드립니다 💝
- 1Panel 온라인 설치 문서
- GitHub Action으로 Shiroi Docker 이미지 빌드하기 · Miku의 오로라 별
- Mix Space + Shiro 완전 컨테이너화 배포 가이드 · 박하의 오두막
- 1Panel 앱 자체 제작 · FIT2CLOUD 커뮤니티 포럼
- 1Panel 앱 자체 제작 도구
- Shiro Markdown 확장 문법 문서
- Yohaku 테마 문서
- NEXT_PUBLIC_CLIENT_API_URL 추가
- API_URL 추가
- 잡담 | Actions로 업스트림 프로젝트 동기화 및 내 브랜치에 병합하기
- GitHub Actions로 Yohaku의 Docker 이미지 빌드하기
여백은 하나의 태도입니다.
당신의 블로그도 그렇게 천천히 펼쳐보고 싶은 편지지가 되길 바랍니다 🌿
