18 Commits
Author SHA1 Message Date
shihaam f7036c2c25 update ci to commit fastlane
Auto Tag on Version Change / check-version (push) Successful in 4s
2026-03-29 01:18:14 +05:00
shihaam 0c27088640 edge-to-edge will haunt forever
Auto Tag on Version Change / check-version (push) Successful in 4s
2026-03-29 00:23:24 +05:00
shihaam 11a349bbad fuck you google, we not edging
Auto Tag on Version Change / check-version (push) Successful in 3s
2026-03-29 00:15:41 +05:00
shihaam 3418e63dde improve caching
Auto Tag on Version Change / check-version (push) Successful in 3s
2026-03-28 22:40:27 +05:00
shihaam 659c79e5dd workflows
Auto Tag on Version Change / check-version (push) Successful in 4s
2026-03-28 22:35:14 +05:00
shihaam 0512662466 ignore key and release apk 2026-03-28 22:35:03 +05:00
shihaam 842bb0553e build..release 2026-03-28 22:34:33 +05:00
shihaam 96e5cc1213 build release using signed key 2026-03-28 22:32:02 +05:00
shihaam 621618a3da update badges links 2026-03-28 08:48:12 +05:00
shihaam b6b05a71de update badges 2026-03-28 08:42:53 +05:00
shihaam 72645c054a add badges 2026-03-28 08:35:02 +05:00
shihaam 676df8e8b3 update docs 2026-03-14 01:34:42 +05:00
shihaam b15c7c8213 update docs 2026-03-14 01:30:09 +05:00
shihaam 7c2f06bceb update docs 2026-03-14 01:18:21 +05:00
shihaam 4a0817b222 update docs 2026-03-14 01:13:03 +05:00
shihaam 2b34d3f2ac update docs 2026-03-14 01:11:26 +05:00
shihaam 085552b759 update docs 2026-03-14 01:06:10 +05:00
shihaam 4379656f16 update docs 2026-03-14 01:01:12 +05:00
13 changed files with 210 additions and 27 deletions
+3
View File
@@ -0,0 +1,3 @@
KEYSTORE_PASSWORD=your_keystore_password_here
KEY_ALIAS=your_key_alias_here
KEY_PASSWORD=your_key_password_here
+2
View File
@@ -0,0 +1,2 @@
.env
release/
+11
View File
@@ -0,0 +1,11 @@
services:
release:
# image: git.shihaam.dev/dockerfiles/android-builder
image: git.shihaam.dev/dockerfiles/runners/gradle
hostname: isodroid
network_mode: host
env_file: .env
volumes:
- ./release:/release
- ../../:/source
- /root/.cache/cache-runners/gradle:/root/.gradle
+70
View File
@@ -0,0 +1,70 @@
name: Auto Tag on Version Change
on:
push:
branches:
- main
jobs:
check-version:
runs-on: docker-compose
steps:
- name: Checkout source code
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.PAT_GITEA }}
- name: Check version and prepare release
id: version
run: |
VERSION=$(grep 'versionName = ' app/build.gradle.kts | sed 's/.*versionName = "\(.*\)".*/\1/')
VERSION_CODE=$(grep 'versionCode = ' app/build.gradle.kts | sed 's/.*versionCode = \([0-9]*\).*/\1/')
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "version_code=$VERSION_CODE" >> $GITHUB_OUTPUT
if git tag -l | grep -q "^v${VERSION}$"; then
echo "Tag v${VERSION} already exists, skipping"
echo "should_release=false" >> $GITHUB_OUTPUT
else
echo "New version detected: v${VERSION}"
echo "should_release=true" >> $GITHUB_OUTPUT
fi
- name: Extract and commit fastlane changelog
if: steps.version.outputs.should_release == 'true'
run: |
VERSION="${{ steps.version.outputs.version }}"
VERSION_CODE="${{ steps.version.outputs.version_code }}"
FASTLANE_DIR="fastlane/metadata/android/en-US/changelogs"
mkdir -p "$FASTLANE_DIR"
awk -v ver="$VERSION" '
BEGIN { found=0 }
/^## \[/ {
if (found) exit
if ($0 ~ "\\[" ver "\\]") { found=1; next }
}
found { print }
' CHANGELOG.md > "$FASTLANE_DIR/${VERSION_CODE}.txt"
git config user.name "Gitea Actions"
git config user.email "actions@gitea.local"
if git diff --quiet "$FASTLANE_DIR/${VERSION_CODE}.txt" 2>/dev/null; then
echo "No changelog changes to commit"
else
git add "$FASTLANE_DIR/${VERSION_CODE}.txt"
git commit -m "Add fastlane changelog for v${VERSION}"
git push origin main
fi
- name: Create and push tag
if: steps.version.outputs.should_release == 'true'
run: |
VERSION="${{ steps.version.outputs.version }}"
git tag "v${VERSION}"
git push origin "v${VERSION}"
echo "Created and pushed tag v${VERSION}"
+79
View File
@@ -0,0 +1,79 @@
name: Build and Release APK
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: docker-compose
steps:
- name: Checkout source code
uses: actions/checkout@v4
- name: Setup keystore and environment
run: |
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > app/key.jks
echo "${{ secrets.DOTENV_BASE64 }}" | base64 -d > .build/release/.env
- name: Build APK
working-directory: .build/release
run: docker compose run --rm release
- name: Extract release notes
run: |
VERSION="${{ gitea.ref_name }}"
VERSION="${VERSION#v}"
awk -v ver="$VERSION" '
BEGIN { found=0 }
/^## \[/ {
if (found) exit
if ($0 ~ "\\[" ver "\\]") { found=1; next }
}
found { print }
' CHANGELOG.md > release_notes.md
- name: Create Gitea Release
env:
GITEA_SERVER_URL: ${{ gitea.server_url }}
GITEA_REPOSITORY: ${{ gitea.repository }}
GITEA_TOKEN: ${{ secrets.PAT_GITEA }}
run: |
TAG="${{ gitea.ref_name }}"
TITLE="ISODroid ${{ gitea.ref_name }}"
NOTES_FILE="release_notes.md"
ASSET_PATH=".build/release/release/ISODroid-${{ gitea.ref_name }}.apk"
API_URL="${GITEA_SERVER_URL}/api/v1/repos/${GITEA_REPOSITORY}/releases"
RELEASE_RESPONSE=$(curl -s -X POST "$API_URL" \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"tag_name\": \"${TAG}\",
\"name\": \"${TITLE}\",
\"body\": $(jq -Rs . < "$NOTES_FILE")
}")
RELEASE_ID=$(echo "$RELEASE_RESPONSE" | jq -r '.id')
if [ "$RELEASE_ID" = "null" ] || [ -z "$RELEASE_ID" ]; then
echo "Failed to create release:"
echo "$RELEASE_RESPONSE"
exit 1
fi
echo "Created release with ID: $RELEASE_ID"
ASSET_NAME=$(basename "$ASSET_PATH")
UPLOAD_URL="${API_URL}/${RELEASE_ID}/assets?name=${ASSET_NAME}"
curl -s -X POST "$UPLOAD_URL" \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/octet-stream" \
--data-binary "@${ASSET_PATH}"
echo "Uploaded asset: $ASSET_NAME"
+2
View File
@@ -16,3 +16,5 @@ local.properties
app/release/
app/debug/
app/key.jks
.build/release/release/*.apk
-10
View File
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
</selectionStates>
</component>
</project>
+19 -5
View File
@@ -1,5 +1,14 @@
# ISO Droid
[![AI Slop Inside](https://sladge.net/badge.svg)](https://sladge.net)
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](LICENSE)
![Android 8.0+](https://img.shields.io/badge/Android-8.0+-3DDC84?logo=android&logoColor=white)
![Kotlin](https://img.shields.io/badge/Kotlin-7F52FF?logo=kotlin&logoColor=white)
![Jetpack Compose](https://img.shields.io/badge/Jetpack%20Compose-4285F4?logo=jetpackcompose&logoColor=white)
[![Root Required](https://img.shields.io/badge/Root-Required-red.svg)](docs/DISCLAIMER.md)
![Maintained](https://img.shields.io/badge/Maintained-yes-green.svg)
[![Gitea Release](https://img.shields.io/gitea/v/release/shihaam/ISODroid?gitea_url=https://git.shihaam.dev&logo=gitea)](https://git.shihaam.dev/shihaam/ISODroid/releases/latest)
> **Note:** This app requires root access and was developed with AI assistance (Claude).
> I use it on my own devices, but as with any root tool, understand what you're running and keep backups.
>
@@ -34,7 +43,7 @@ Android app for mounting ISO/IMG files as USB mass storage or CD-ROM devices on
| F-Droid | Gitea Releases |
|:--:|:--:|
| [![Get it on F-Droid](https://f-droid.org/badge/get-it-on.png)](https://f-droid.org/en/packages/sh.sar.isodroid) | [Download latest APK](https://git.shihaam.dev/shihaam/ISODroid/releases) |
| <!-- [![Get it on F-Droid](https://f-droid.org/badge/get-it-on.png)](https://f-droid.org/en/packages/sh.sar.isodroid) --> [Coming Soon](https://gitlab.com/fdroid/fdroiddata/-/merge_requests/34539) | [Download latest APK](https://git.shihaam.dev/shihaam/ISODroid/releases/latest) |
## Installation
@@ -67,14 +76,19 @@ Android app for mounting ISO/IMG files as USB mass storage or CD-ROM devices on
Around 2014/2015 I rooted my first phone and went down the rabbit hole of what you could do with a rooted Android.
That's when I found [DriveDroid](https://softwarebakery.com/projects/drivedroid) ([archive](https://web.archive.org/web/20160901000000*/https://softwarebakery.com/projects/drivedroid)) by Software Bakery. Teenage me thought it was the coolest thing ever.
fastforwrd few years: DriveDroid stopped being maintained, 404 on playstore, and even if you find apk floating around ([Kali NetHunter](https://store.nethunter.com/packages/com.softwarebakery.drivedroid/) keeps one) it does not work on modern Android. I've wanted something like it for years.
Then I found [isodrive](https://github.com/nitanmarcel/isodrive) by nitanmarcel ([mirror](https://git.shihaam.dev/shihaam/isodrive)). Tried it. It worked. So here we are. ISO Droid is basically me chasing that teenage nostalgia and trying to make something simple that just works.
The name comes from **Drive**Droid + **iso**drive = **ISO Droid**. Not very creative, but it fits.
Fast forward a 10 years: DriveDroid is no longer maintained, 404 on Play Store, and even if you find an APK ([Kali NetHunter App Store](https://store.nethunter.com/packages/com.softwarebakery.drivedroid/) keeps one) it does not work on modern Android.
As someone who gives tech support often to relatives and friends, I've wanted something like it for years. Having Linux to chroot or HBCD for Windows password reset at anytime without having to carry around USB drives is very useful for me.
Then I found [isodrive](https://github.com/nitanmarcel/isodrive) by nitanmarcel ([mirror](https://git.shihaam.dev/shihaam/isodrive)). I tried the Magisk module and Termux, it worked. So here we are.
ISO Droid is basically me chasing that teenage nostalgia and trying to make something simple that just works.
The name comes from **iso**drive + Drive**Droid** = **ISO Droid**.
Special thanks to:
- [isodrive](https://github.com/nitanmarcel/isodrive) by nitanmarcel - The CLI tool that powers ISO Droid
- [ISODriveUT](https://github.com/fredldotme/ISODriveUT) by fredldotme - Original inspiration for isodrive
- [DriveDroid](https://softwarebakery.com/projects/drivedroid) by Software Bakery - where it all started
- [DriveDroid](https://softwarebakery.com/projects/drivedroid) by Software Bakery - where it all started for me
- [Simple Icons](https://simpleicons.org/) and [SVG Repo](https://www.svgrepo.com/) for OS icons
## License
+10
View File
@@ -136,8 +136,18 @@ android {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
signingConfigs {
create("release") {
storeFile = file("key.jks")
storePassword = System.getenv("KEYSTORE_PASSWORD")
keyAlias = System.getenv("KEY_ALIAS")
keyPassword = System.getenv("KEY_PASSWORD")
}
}
buildTypes {
release {
signingConfig = signingConfigs.getByName("release")
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
@@ -12,8 +12,6 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
@@ -103,7 +101,6 @@ fun DownloadsScreen(
}
Scaffold(
contentWindowInsets = WindowInsets(0),
topBar = {
TopAppBar(
title = { Text("Download ISOs") },
@@ -127,7 +124,6 @@ fun DownloadsScreen(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.navigationBarsPadding()
.verticalScroll(rememberScrollState())
) {
groupedDownloads.forEach { (category, osList) ->
@@ -10,8 +10,6 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
@@ -143,7 +141,6 @@ fun MainScreen(
}
Scaffold(
contentWindowInsets = WindowInsets(0),
topBar = {
TopAppBar(
title = { Text("ISO Droid") },
@@ -207,7 +204,6 @@ fun MainScreen(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.navigationBarsPadding()
.nestedScroll(pullToRefreshState.nestedScrollConnection)
) {
if (uiState.isLoading && !pullToRefreshState.isRefreshing) {
@@ -22,8 +22,6 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
@@ -153,7 +151,6 @@ fun SettingsScreen(
}
Scaffold(
contentWindowInsets = WindowInsets(0),
topBar = {
TopAppBar(
title = { Text("Settings") },
@@ -177,7 +174,6 @@ fun SettingsScreen(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.navigationBarsPadding()
.verticalScroll(rememberScrollState())
) {
// Storage section
@@ -5,6 +5,7 @@
package sh.sar.isodroid.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
@@ -13,7 +14,11 @@ import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
@@ -42,6 +47,15 @@ fun ISODroidTheme(
else -> LightColorScheme
}
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
WindowCompat.setDecorFitsSystemWindows(window, false)
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme
}
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,