app-store/README.md
2026-05-13 04:01:37 +00:00

223 lines
6.3 KiB
Markdown

# Local App Store
A private Android app store for your local network. Serve APKs from your own repository and install them on your devices via the Android app or browse/download from any browser.
## Architecture
- **Server**: FastAPI application that serves APKs and metadata over HTTP
- **Android Client**: Kotlin app with Material Design 3 UI, Room database, and PackageInstaller integration
- **Web Frontend**: Single-page HTML/JS app served by the server, accessible from any browser
- **Signing**: Self-signed keystore for both the store app and distributed apps
## Quick Start
### 1. Server Setup
```bash
cd server
# Activate virtual environment
source venv/bin/activate
# Install dependencies (if not already done)
pip install -r requirements.txt
# Drop your APKs into the repos/ directory
cp /path/to/your/app.apk repos/
# Start the server (runs on all interfaces, port 8080)
python main.py
```
The server auto-scans `repos/` on startup. To rescan after adding new APKs:
```bash
curl -X POST http://localhost:8080/api/scan
```
You can also add app metadata (name, description, icon, screenshots) by creating a JSON file alongside each APK:
```
repos/
myapp.apk
myapp.json # Optional metadata override
myapp_screenshots/ # Optional screenshots (*.png)
```
Example `myapp.json`:
```json
{
"name": "My App",
"description": "A great app for doing things",
"icon": "res/mipmap-hdpi/ic_launcher.png",
"screenshots": ["screenshot1.png", "screenshot2.png"]
}
```
### 2. Web Frontend
Once the server is running, open any browser and navigate to:
```
http://<server-ip>:8080
```
The web frontend provides:
- Browse all apps in a responsive grid
- Search apps by name or description
- View app details (description, permissions, screenshots)
- Download APK files directly
- Scan repository for new apps
### 3. Signing APKs
```bash
cd signing
# Generate keystore (already done - localstore.keystore exists)
# To regenerate: ./generate-key.sh
# Sign an APK before adding it to repos/
./sign-apk.sh /path/to/your/app.apk
```
All APKs should be signed with the same keystore for consistency.
### 4. Building the Android Client
Prerequisites: Java 17, Android SDK (platform-tools, build-tools 34.0.0, platforms android-34).
```bash
cd android
# Configure SDK path (adjust to your SDK location)
echo "sdk.dir=/path/to/android-sdk" > local.properties
# Build debug APK
./gradlew :app:assembleDebug
# Build release APK (signed with localstore.keystore)
./gradlew :app:assembleRelease
```
APKs are output to `app/build/outputs/apk/`.
### 5. Installing on Android Device
```bash
# Connect device via USB or use wireless ADB
# Install debug build
/home/jarian/android-sdk/platform-tools/adb install app/build/outputs/apk/debug/app-debug.apk
# Install release build
/home/jarian/android-sdk/platform-tools/adb install app/build/outputs/apk/release/app-release.apk
```
First time setup in the app:
1. Open the app -> go to Settings
2. Enter your server IP address (e.g., `192.168.8.128`)
3. Click "Test Connection" to verify
4. Click "Save"
5. Grant "Install unknown apps" permission when prompted
## API Endpoints
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/` | Web frontend |
| GET | `/api/apps` | List all apps (supports `?search=`, `?offset=`, `?limit=`) |
| GET | `/api/apps/{id}` | Get app details |
| GET | `/api/apps/{id}/download` | Download APK file |
| GET | `/api/apps/{id}/icon` | Get app icon (128x128 PNG) |
| GET | `/api/apps/{id}/screenshots` | List screenshots |
| GET | `/api/apps/{id}/screenshots/{file}` | Get screenshot image |
| POST | `/api/apps/update-check` | Check for updates |
| POST | `/api/scan` | Trigger repository rescan |
| GET | `/api/status` | Server status info |
## Configuration
Server config is in `server/config.yaml`:
```yaml
server:
host: "0.0.0.0" # Bind to all interfaces
port: 8080 # HTTP port
repository:
path: "./repos" # APK storage directory
```
## Project Structure
```
app-store/
server/
main.py # FastAPI application + web frontend
app.py # Pydantic models
scanner.py # APK metadata scanner
config.yaml # Server configuration
requirements.txt # Python dependencies
venv/ # Python virtual environment
repos/ # Drop APKs here
web/
index.html # Web frontend (HTML/CSS/JS)
android/ # Gradle project (CLI build)
build.gradle # Root build config
settings.gradle # Project settings
local.properties # SDK path
app/
build.gradle # App build config (KSP, Room, etc.)
src/main/
AndroidManifest.xml
kotlin/com/localstore/
LocalStoreApp.kt
data/ # API, models, database, repository, settings
ui/ # Fragments, ViewModels, adapters
installer/ # PackageInstaller wrapper
res/ # Layouts, strings, themes, navigation, colors
gradle/ # Gradle wrapper
signing/
generate-key.sh # Create keystore
sign-apk.sh # Sign APKs
localstore.keystore # Self-signed keystore
app-debug.apk # Debug build
app-release.apk # Release build (signed)
README.md
```
## Android Permissions
The app requires:
- `INTERNET` - connect to the server
- `REQUEST_INSTALL_PACKAGES` - install APKs (user grants in Settings)
- `ACCESS_NETWORK_STATE` - check connectivity
## Security Notes
- The keystore is self-signed. For production use, consider a proper CA-signed certificate.
- The server serves over HTTP. For HTTPS, configure a reverse proxy (nginx/Caddy) with Let's Encrypt.
- All APKs should be signed with the same keystore to ensure trust chain.
## Troubleshooting
**Apps not appearing after adding to repos/**
- Run `POST /api/scan` or restart the server
**Icon not loading**
- The server extracts icons from APK resources. If extraction fails, specify icon path in the `.json` metadata file.
**Installation fails**
- Ensure "Install unknown apps" permission is granted for the app store app in Android Settings
**Connection fails**
- Check server IP in Settings matches your server's actual IP
- Ensure both devices are on the same network
- Check firewall allows port 8080
**Gradle build fails**
- Ensure Java 17 is installed and JAVA_HOME is set
- Ensure Android SDK is installed and `local.properties` points to it
- Run `./gradlew :app:clean` before rebuilding