모바일/데스크톱 앱 프로젝트의 자동 빌드 & HTTP 서빙 인프라. git commit 시 자동으로 빌드하고, 웹 UI에서 아티팩트를 다운로드할 수 있다. (기본 포트: 8888)
- 플랫폼 무관: Android(Gradle), Flutter, iOS(fastlane), macOS 등
buildCommand에 뭐든 지정 가능 - 최소 설정: 프로젝트 루트에
.tiny_ci.json하나 +register.sh한 번 실행 - 웹 UI: 빌드 상태, 히스토리, 실시간 로그, 수동 빌드 버튼, 아티팩트 다운로드
# Homebrew
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Java 17 (Android/Gradle 빌드에 필요)
brew install openjdk@17
# Flutter SDK (Flutter 프로젝트만 해당)
brew install --cask flutterPython 3는 macOS에 기본 포함되어 있어 별도 설치 불필요.
# 1. tiny_ci 클론 (원하는 경로에)
git clone <repo-url> /path/to/tiny_ci
# 2. 초기 설정 (디렉토리 생성 + LaunchAgent 등록)
/path/to/tiny_ci/install.sh # 기본 포트 8888
/path/to/tiny_ci/install.sh --port 9000 # 포트 변경 시
# 3. 앱 저장소 등록
cd /path/to/my-app
/path/to/tiny_ci/scripts/register.sh완료. 이후 git commit 하면 자동 빌드 → http://localhost:<PORT> 에서 확인.
install.sh는 이미 등록된 프로젝트들의 git hook도 최신 방식으로 다시 동기화한다. 또한 기존projects/*.json에sourceRepoPath/branch가 없으면 현재 설정을 기준으로 자동 보정한다.
참고:
register.sh는 실행 시점의 tiny_ci 경로를 git hook에 기록합니다. tiny_ci를 다른 경로로 이동한 경우 각 프로젝트에서register.sh를 다시 실행하세요.
- tiny_ci는 개발 중인 워킹카피를 직접 빌드하지 않는다.
- 등록된 각 프로젝트마다
tiny_ci/workspaces/<project-id>/에 전용 clone/workspace를 두고 여기서 빌드한다. - 프로젝트 JSON의
branch에 지정된 브랜치만 fetch/checkout 해서 빌드한다. - git hook도 pinned branch와 현재 커밋 브랜치가 다르면 자동 빌드를 건너뛴다.
- 반면
watchArtifacts는 개발용 원본 저장소의 로컬 빌드 산출물을 감시한다.
{
"id": "my-app",
"name": "My App",
"branch": "main",
"buildCommand": "flutter build apk --release",
"buildWorkingDir": "platforms/flutter",
"artifactPath": "platforms/flutter/build/app/outputs/flutter-apk/app-release.apk",
"artifactName": "MyApp.apk"
}| 필드 | 필수 | 설명 |
|---|---|---|
id |
✓ | 프로젝트 식별자 (영문 소문자, 하이픈) |
name |
✓ | 표시 이름 |
branch |
권장 | tiny_ci가 빌드할 고정 브랜치 (main 권장) |
buildCommand |
✓ | 빌드 명령어 (쉘에서 그대로 실행됨) |
buildWorkingDir |
✓ | 빌드 실행 디렉토리 (저장소 루트 기준 상대경로 권장) |
artifactPath |
✓ | 빌드 결과물 경로 (저장소 루트 기준 상대경로 권장) |
artifactName |
✓ | 서빙될 파일명 |
watchArtifacts |
— | 로컬 빌드 감지 대상 목록 (아래 참고) |
branch 를 생략하면 register.sh 실행 시점의 현재 브랜치가 기본값으로 저장된다. 다만 실수로 feature branch를 서빙하지 않으려면 .tiny_ci.json 에 명시하는 편이 안전하다.
cd /path/to/my-app
/path/to/tiny_ci/scripts/register.sh등록되면 tiny_ci는 프로젝트의 .tiny_ci.json 을 projects/<id>.json 으로 복사하면서, 머신 로컬 정보인 sourceRepoPath 를 추가한다.
프로젝트 저장소에서 직접 빌드한 아티팩트를 웹 UI에 별도 버튼으로 노출할 수 있다.
{
"id": "my-android-app",
"branch": "main",
"artifactName": "MyApp-debug.apk",
...
"watchArtifacts": [
{
"label": "Debug",
"path": "app/build/outputs/apk/debug/app-debug.apk",
"file": "MyApp-debug.apk"
},
{
"label": "Release",
"path": "app/build/outputs/apk/release/app-release.apk",
"file": "MyApp-release.apk"
}
]
}| 필드 | 설명 |
|---|---|
label |
버튼 표시 이름 (Debug / Release 등) |
path |
원본 저장소 내 아티팩트 경로 (상대경로 가능) |
file |
serve 디렉토리에 저장될 파일명 |
표시 규칙
| 버튼 | 표시 조건 | 배지 |
|---|---|---|
Download Stable |
status=ready이면 항상 |
— |
{label} Latest |
file이 artifactName과 다른 경우 파일이 존재하면 항상 |
최신 빌드면 Latest |
{label} Latest |
file이 artifactName과 같은 경우 (tiny_ci도 빌드) |
로컬이 더 최신일 때만 |
등록 후 /api/scan/{id} 호출 시 원본 저장소 파일과 마지막 tiny_ci 빌드를 비교해 자동 감지·복사한다.
{
"id": "my-android",
"name": "My Android App",
"branch": "main",
"buildCommand": "./gradlew assembleDebug",
"buildWorkingDir": ".",
"artifactPath": "app/build/outputs/apk/debug/app-debug.apk",
"artifactName": "MyApp-debug.apk"
}Release 빌드도 배포하려면 debug keystore로 서명 설정 권장:
// app/build.gradle.kts buildTypes { release { signingConfig = signingConfigs.getByName("debug") } }
{
"id": "my-flutter",
"name": "My Flutter App",
"branch": "main",
"buildCommand": "flutter build apk --release",
"buildWorkingDir": ".",
"artifactPath": "build/app/outputs/flutter-apk/app-release.apk",
"artifactName": "MyApp.apk"
}playUpload 설정이 enabled여도 자동 업로드는 안 된다 — git push로 들어오는 일반 빌드는 artifact만 만들고 멈춘다. Play Console로 올리려면 수동 트리거가 필요하다:
- Web UI: 프로젝트 detail 패널의
⬆ Release버튼 (playUpload.enabled: true인 프로젝트에만 표시) - HTTP:
POST /api/release/<project-id>(/api/build/...와 비슷하지만 빌드 성공 시 Play 업로드까지 진행)
내부적으로는 TINY_CI_RELEASE=1 환경 변수가 build.sh에 전달되고, upload_to_play_if_configured()가 그 플래그가 있을 때만 동작한다.
설정 자체는 다음과 같다.
{
"id": "my-flutter",
"buildCommand": "flutter build apk --release --build-number \"$TINY_CI_BUILD_NUMBER\" && flutter build appbundle --release --build-number \"$TINY_CI_BUILD_NUMBER\"",
"artifactPath": "build/app/outputs/flutter-apk/app-release.apk",
"artifactName": "MyApp.apk",
"playUpload": {
"enabled": true,
"packageName": "com.example.myapp",
"track": "internal",
"artifactPath": "build/app/outputs/bundle/release/app-release.aab",
"artifactType": "aab",
"serviceAccountJson": "/Users/testors/.config/tiny_ci/google-play/myapp-service-account.json",
"releaseStatus": "completed",
"changesInReviewBehavior": "ERROR_IF_IN_REVIEW"
}
}| 필드 | 설명 |
|---|---|
enabled |
true면 빌드 성공 후 업로드를 실행 |
packageName |
Android applicationId / Play Console 패키지명 |
track |
Internal Testing은 internal |
artifactPath |
Play에 올릴 파일. 생략하면 tiny_ci의 artifactPath를 사용 |
artifactType |
auto, apk, aab 중 하나. 신규 Play 앱은 보통 AAB 사용 |
serviceAccountJson |
Google Play Developer API 권한이 있는 서비스 계정 JSON |
releaseStatus |
일반적으로 completed |
changesInReviewBehavior |
기본값은 기존 심사 중 변경을 건드리지 않는 ERROR_IF_IN_REVIEW |
TINY_CI_BUILD_NUMBER는 tiny_ci가 빌드마다 Unix timestamp로 자동 제공한다. Flutter의 --build-number에 넣어 Play versionCode 중복 업로드 실패를 피한다.
최초 설정은 Play Console에서 한 번 해야 한다.
- Play Console에 앱을 생성하고 패키지명을 확정한다.
- Testing > Internal testing에서 tester email list 또는 Google Group을 등록한다.
- Google Cloud에서 Google Play Developer API를 enable한다.
- 서비스 계정을 만들고 Play Console Users and permissions에 초대한다. 최소한 testing track release를 만들 수 있는 권한이 필요하다.
- 서비스 계정 JSON을
serviceAccountJson경로에 저장한다. 이 파일은 git에 커밋하지 않는다. - Android release 빌드는 debug key가 아니라 Play 업로드용 release/upload key로 서명되도록 앱 프로젝트에서 설정한다.
{
"id": "my-ios",
"name": "My iOS App",
"branch": "main",
"buildCommand": "fastlane gym --scheme MyApp --output_directory build",
"buildWorkingDir": ".",
"artifactPath": "build/MyApp.ipa",
"artifactName": "MyApp.ipa"
}tiny_ci/
├── scripts/
│ ├── build.sh # 범용 빌드 스크립트 (project-id를 인자로 받음)
│ └── register.sh # 프로젝트 등록 + git hook 설치
├── server.py # HTTP 서버 (정적 파일 + REST API)
├── projects/ # 등록된 프로젝트 설정 (register.sh가 자동 생성)
│ └── my-app.json
├── workspaces/ # 프로젝트별 전용 clone/workspace
│ └── my-app/
├── serve/ # HTTP 서빙 루트 (기본 port 8888)
│ ├── index.html # 웹 UI
│ ├── projects.json # 전체 프로젝트 목록 (자동 갱신)
│ └── my-app/
│ ├── build-status.json
│ ├── build-history.json
│ ├── artifacts.json # watchArtifacts 스캔 결과
│ ├── build.log # 빌드 로그 (빌드 중 실시간 접근 가능)
│ └── MyApp.apk
├── logs/ # 빌드 로그 아카이브 (프로젝트별, 최근 10개 보관)
├── install.sh # 최초 설치 (LaunchAgent 등록)
└── serve.sh # HTTP 서버 수동 실행
| 메서드 | 경로 | 설명 |
|---|---|---|
GET |
/api/scan/{id} |
원본 저장소 로컬 빌드 감지·복사, artifacts.json 갱신 |
POST |
/api/build/{id} |
빌드 수동 트리거 (build.sh 백그라운드 실행) |
macOS
launchctl kickstart -k gui/$(id -u)/com.tiny_ci # 재시작
launchctl bootout gui/$(id -u)/com.tiny_ci # 중지Linux
systemctl --user restart tiny_ci # 재시작
systemctl --user stop tiny_ci # 중지
systemctl --user status tiny_ci # 상태 확인공통
# 특정 프로젝트 수동 빌드
/path/to/tiny_ci/scripts/build.sh my-app
# 빌드 로그 실시간 확인
tail -f /path/to/tiny_ci/serve/my-app/build.log
# 프로젝트 설정 재등록 (tiny_ci 경로 변경 등)
cd /path/to/my-app && /path/to/tiny_ci/scripts/register.sh