A portable Gitea Git server that runs on your Android device
Start the server and you have a full-featured Git web interface accessible from any browser at http://<device-ip>:3000 — no cloud, no setup, just a portable Git server in your pocket.
Getting Started • Architecture • Building • FAQ • Contributing
- Full Gitea v1.22.6 — Issues, Pull Requests, repositories, wiki, Code, actions, and all Gitea features
- Runs via Foreground Service — Server stays alive even when the app is in the background
- In-app WebView — Access Gitea's UI directly within the app, no browser needed
- Network-accessible — Any device on your LAN can reach the server at
http://<device-ip>:3000 - Port configuration — Choose any port before starting the server
- Dump & Restore — Full data backup/restore via Android's Storage Access Framework (SAF)
- SQLite storage — Zero external database dependencies; everything runs on-device
- Offline mode — Fully air-gapped; no internet connection required
- Download the latest
app-debug.apkfrom the Releases page - Sideload it on your Android device (enable Install from unknown sources)
- Open the iGitEra app
- Tap Start Server — the Gitea server starts as a foreground service
- Tap Open Browser to access Gitea through the in-app browser
- Access from any device on your network at
http://<device-ip>:3000
First-time user? The default credentials for new Gitea installations are created on first register — anyone can register since registration is open by default. You can lock this down in Gitea's admin panel.
When you tap Start Server:
- The app starts a Foreground Service to keep the process alive
- JNI loads
libgiteawrapper.so(the Go bridge, ~2 MB) - The Go bridge writes
app.iniconfiguration to the app's data directory - It
fork/execslibgiteaserver.so(the Gitea binary, ~113 MB) as a subprocess - Gitea starts listening on the configured port using SQLite3 storage
- A background goroutine periodically ensures all git repos are properly initialized
To stop, tap Stop Server — the Go bridge sends SIGTERM to the Gitea subprocess, waits up to 10 seconds, then force-kills if needed.
| Main Screen | WebView UI | Server Running |
|---|---|---|
| (coming soon) | (coming soon) | (coming soon) |
┌──────────────────────────────────────────────────────────┐
│ Android APK (com.igitera) │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ MainActivity │ │ WebViewActivity │ │
│ │ (start/stop) │─────────▶│ (in-app Gitea UI) │ │
│ └────────┬─────────┘ └──────────────────┘ │
│ │ │
│ ┌────────▼─────────┐ │
│ │ GiteaService │ Foreground Service (dataSync) │
│ │ (lifecycle) │ Stay alive in background │
│ └────────┬─────────┘ │
│ │ JNI (System.loadLibrary) │
│ ┌────────▼──────────────────┐ │
│ │ libgiteawrapper.so │ Go → C shared library │
│ │ (subprocess manager) │ ~2 MB │
│ │ ─ StartGiteaServer() │ ZERO external deps │
│ │ ─ StopGiteaServer() │ │
│ │ ─ IsServerRunning() │ │
│ └────────┬──────────────────┘ │
│ │ fork/exec │
│ ┌────────▼──────────────────┐ │
│ │ libgiteaserver.so │ Gitea v1.22.6 PIE binary │
│ │ (Gitea web server) │ ~113 MB │
│ │ ─ gogit (pure-Go git) │ jniLibs → native lib dir │
│ │ ─ sqlite3 (embedded DB) │ SELinux: apk_data_file ✓ │
│ │ ─ bindata (assets) │ │
│ └───────────────────────────┘ │
│ │
│ ┌───────────────────────────┐ │
│ │ libgitshim.so │ Git stub binary │
│ │ (git version shim) │ ~2 MB │
│ └───────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
MainActivity.kt— Launcher activity with Start/Stop/Web UI controls, port input, and Dump/Restore buttons. Polls server status and updates UI accordingly.GiteaService.kt— Android Foreground Service (dataSynctype). Manages the server lifecycle with a persistent notification. Shows the server URL and a "Stop" action.WebViewActivity.kt— Embedded Chromium WebView that loads the Gitea UI. Allows in-app zoom, intercepts external links, and handles back-navigation through browser history.GiteaBridge.kt— Kotlinobjectdeclaringexternal funJNI methods. Loadslibgiteawrapper.soand exposesstartServer(),stopServer(),isRunning().BackupManager.kt— Creates and restores ZIP backups of the entire Gitea data directory via Android's Storage Access Framework.NetworkUtils.kt— Detects the device's LAN IPv4 address, prioritizing WiFi/Ethernet over mobile data.
main.go— Exports three C functions (StartGiteaServer,StopGiteaServer,IsServerRunning). Creates the data directory, writesapp.ini,fork/execs the Gitea binary, monitors its lifetime, and runs a background goroutine that fixes uninitialized git repos.settings.go— Generates Gitea'sapp.iniconfiguration on startup. Auto-detects LAN IP for correct clone URLs. Also containsensureBareRepoInit()— a workaround for go-git'sPlainInitsilently failing on some Android filesystems.jni_android.c— JNI C wrappers using standardJava_com_igitera_GiteaBridge_*naming convention (notRegisterNatives). Converts between JNI types and C types.build-android.sh— All-in-one build script that cross-compiles all components for Android arm64-v8a.gitea-patches.patch— Fixes a "concurrent map read and map write" panic in Gitea'smodules/setting/setting.goby adding async.Mutexaround theconfiguredPathsmap.
A full Gitea v1.22.6 server cross-compiled with these build tags:
gogit— Pure-Go git implementation (go-git). No systemgitbinary needed.sqlite3— Embedded database via CGO. Requires the NDK'saarch64-linux-android28-clang.bindata— HTML templates, CSS, JS, and images are compiled into the binary. Requires building webpack assets first.
A minimal statically-linked C binary (~2 MB) that handles exactly three commands:
git version→ reportsgit version 2.45.0git config→ returns success (exit 0)git rev-parse→ returns success (exit 0)
With the gogit build tag, Gitea never calls real git for operations — but it does check git version at startup, and the shim satisfies that check.
| Decision | Rationale |
|---|---|
| Subprocess architecture | The Go wrapper fork/execs Gitea as a separate process instead of importing it as a library. This avoids Gitea's ever-changing internal API and keeps the JNI bridge at ~2 MB instead of ~95 MB. |
| jniLibs packaging | Android .apk files are ZIP archives. Native .so files in jniLibs/ are extracted by the package manager to the native library directory, where SELinux policy (apk_data_file context) allows execve(). The app's private data directory (/data/data/com.igitera/) is mounted noexec. |
gogit build tag |
Android ships without a git binary. The gogit tag makes Gitea use the pure-Go go-git library instead. Skip this tag and Gitea will look for a git binary and fail. |
bindata build tag |
Embeds all frontend assets directly into the binary. Without this, Gitea reads templates, CSS, and JS from the filesystem — which requires them to be shipped separately and accessible at the right paths. |
| Git shim | Even with gogit, Gitea calls git version on startup to verify a sane git is available, and calls git config and git rev-parse in some initialization paths. The shim returns just enough to pass these checks. |
| Foreground Service | Android aggressively kills background processes to save battery. A Foreground Service with a persistent notification tells the OS "this is important, don't kill it." The dataSync foreground service type is used for API 34+ compatibility. |
| JNI naming convention | Uses standard Java_com_igitera_GiteaBridge_* naming instead of RegisterNatives. Standard JNI naming is simpler and avoids calling-convention mismatches that arise because Go-exported C functions don't receive (JNIEnv*, jclass) parameters. |
iGitEra/
├── README.md # This file
├── LICENSE # MIT License
├── CLAUDE.md # Claude Code instructions
├── CONTRIBUTING.md # Contribution guidelines
├── SECURITY.md # Security policy
├── docs/
│ ├── ARCHITECTURE.md # Deep architecture dive
│ ├── DEVELOPMENT.md # Development setup guide
│ ├── TROUBLESHOOTING.md # Common issues & fixes
│ └── API.md # JNI bridge API reference
├── android/
│ ├── app/
│ │ ├── build.gradle.kts # Android app build config
│ │ ├── proguard-rules.pro # ProGuard optimization rules
│ │ └── src/main/
│ │ ├── AndroidManifest.xml # Permissions, activities, services
│ │ ├── kotlin/com/igitera/
│ │ │ ├── MainActivity.kt # Launcher UI with start/stop controls
│ │ │ ├── GiteaService.kt # Foreground service lifecycle
│ │ │ ├── GiteaBridge.kt # JNI native method declarations
│ │ │ ├── WebViewActivity.kt # In-app Gitea browser
│ │ │ ├── BackupManager.kt # ZIP backup/restore
│ │ │ └── NetworkUtils.kt # LAN IP detection
│ │ ├── jniLibs/arm64-v8a/
│ │ │ ├── libgiteawrapper.so # JNI bridge (~2 MB)
│ │ │ ├── libgiteaserver.so # Gitea binary (~113 MB)
│ │ │ └── libgitshim.so # Git stub (~2 MB)
│ │ └── res/ # Layouts, drawables, strings
│ ├── gitea-wrapper/
│ │ ├── main.go # C-exported Go entry points
│ │ ├── settings.go # Config generation + repo init fix
│ │ ├── jni_android.c # JNI bridge (C side)
│ │ ├── build-android.sh # Cross-compile script
│ │ ├── go.mod # Zero external dependencies
│ │ ├── gitea-patches.patch # Gitea concurrency fix
│ │ ├── gitea-src/ # Gitea source (clone manually)
│ │ └── git-src/ # Git source (clone manually)
│ ├── build.gradle.kts # Root Gradle build
│ ├── settings.gradle.kts # Gradle module settings
│ ├── gradle.properties # JVM args, AndroidX
│ ├── release.keystore # Debug signing key
│ └── gradlew / gradlew.bat # Gradle wrapper
└── .claude/
└── settings.local.json # Claude Code settings
Full build instructions are in docs/DEVELOPMENT.md. The following is a quick summary.
- Go 1.22+ — for the Go wrapper and Gitea cross-compilation
- Android NDK r25+ — for CGO cross-compilation to arm64-v8a
- Android SDK 34 (compileSdk 35) — for the Kotlin app
- Java 17+ — for Gradle
- Node.js + npm — for building Gitea frontend assets (CSS/JS)
cd android/gitea-wrapper
# Set your NDK path (or let the script auto-detect it)
export ANDROID_NDK_HOME=/path/to/android-ndk
# Build ALL native components + APK
./build-android.sh
cd ..
./gradlew assembleDebug
# Install on connected device
adb install app/build/outputs/apk/debug/app-debug.apkSee docs/DEVELOPMENT.md for detailed instructions on building each component separately:
Gitea server logs are stored in the app's private data directory:
# Gitea wrapper logs (Go bridge)
adb shell run-as com.igitera cat files/gitea-data/gitea.log
# Gitea subprocess output (stdout + stderr)
adb shell run-as com.igitera cat files/gitea-data/gitea-output.log
# Android logcat (app-level logging)
adb logcat -s GiteaService GiteaBridge| State | What It Means | Action |
|---|---|---|
| Running on port X | Gitea is up and accepting connections | Open browser or connect from another device |
| Starting... | The Go wrapper is initializing and fork/execing Gitea |
Wait a few seconds |
| Stopped | Server is not running | Tap Start to begin |
| Failed to start | Gitea exited shortly after launch | Check gitea.log and gitea-output.log |
iGitEra includes full data backup and restore:
- Dump Data — Stops the server (if running), opens the Android file-saver dialog, and writes a ZIP of the entire
gitea-datadirectory. After completion, the server restarts automatically if it was running. - Restore Data — Stops the server (if running), opens the file-picker for a ZIP file, wipes the current data directory, and extracts the backup. The server stays stopped after restore — tap Start to resume.
The backup ZIP contains all repos, the SQLite database, LFS objects, and uploaded attachments.
No. The app works on any standard Android device (API 28+/Android 9+). The native libraries are extracted by the package manager to a location where SELinux allows execution.
No. The server runs fully offline. Network access is only required for other devices on your LAN to connect.
Termux runs Gitea in a Linux container on Android. This project bundles Gitea as a compiled binary directly — no container, no Termux, no extra setup. It's a single APK install.
Yes. Any device on the same network can clone/push/pull at http://<phone-ip>:3000/<username>/<repo>.git. The Go wrapper auto-detects the LAN IP and writes it into ROOT_URL.
Currently arm64-v8a only (most modern Android phones and tablets). ARM32 (armeabi-v7a) and x86_64 could be added with additional cross-compilation toolchains.
Yes. Enter a port number in the Port field before starting the server. The default is 3000. After the server starts, the port field is locked until you stop it.
No. SSH is disabled in the generated configuration. The app focuses on HTTP(S) access only.
The Gitea binary is compiled from source at gitea-src/. To update, switch the git tag to a newer Gitea release and rebuild. Note that the API/behavior may change — see docs/DEVELOPMENT.md.
Check the logs (see Debugging). Common causes:
- The port is already in use
- The Gitea binary is corrupted or missing
- SELinux is blocking execution (unlikely on stock Android, common on custom ROMs)
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
If you discover a security vulnerability, please see SECURITY.md for reporting instructions.
MIT © iGitEra