Skip to content

FrancoETrujillo/ApkDepot

Repository files navigation

Disclaimer: This project is a Work in Progress (WIP). It runs fine, but hasn't been thoroughly tested yet.

ApkDepot

A self-hosted, private Android App Store built with Kotlin Multiplatform.

ApkDepot serves two main purposes:

  1. For Enthusiasts: A simplified, self-hosted Play Store alternative. Upload APKs once, then install/update them from an Android client or web dashboard.
  2. For Developers: A full-stack Compose Multiplatform reference project featuring shared UI, state, and networking across Android, Wasm (Web), and Ktor (Backend).

Showcase

Quick Look

Web Client: App Details
Web Client: App Details
Android Client: Update Flow
Android Client: Update Flow

Key Features

  • Private APK Library: Centralize your family/personal APK collection.
  • Frictionless Updates: Android client detects installed versions and offers one-tap updates.
  • Single Deployable: Backend + API + Bundled Wasm Web UI in one executable JAR.
  • Modern Tech Stack: 100% Kotlin (Ktor, SQLDelight, Koin, Compose Multiplatform).
📸 View more screenshots

Public Store Home

Public Store Home

Admin Dashboard

Admin Dashboard

Admin Management

Login Upload Release Tokens
Admin Login Upload Release Token Management

Setup & Deployment

Quick start (local dev)

Clone the repo and run the full stack:

./gradlew :server:runFullStack

Default Behavior:

  • HTTP only: Starts on http://0.0.0.0:8083 (no SSL/HTTPS enabled by default).
  • Local Storage: Creates apkdepot.db and an apk_storage/ folder in the project root.

Then open:

  • Public dashboard: http://localhost:8083
  • Admin dashboard: http://localhost:8083/#admin (admin credentials are set on first run)

Build a single “all-in-one” executable

This builds one runnable JAR containing:

  • Ktor backend/API
  • the compiled Wasm web UI bundled as static assets
./gradlew :server:buildDeployable

Run it (Defaults):

java -jar server/build/libs/server-all.jar

Run with Environment Variables (Example):

To change the port or storage location:

export PORT=8090
export APK_STORAGE_ROOT=/var/data/apkdepot
java -jar server/build/libs/server-all.jar

Docker (untested)

The Ktor plugin includes a Docker task:

./gradlew :server:runDocker

Configuration

The server is configured via application.conf:

  • server/src/main/resources/application.conf

You can override settings via environment variables:

Env Variable Description
PORT Server port
SSL_PORT HTTPS port
SSL_KEYSTORE_PATH Path to keystore
SSL_KEYSTORE_ALIAS Keystore alias
SSL_KEYSTORE_PASSWORD Keystore password
SSL_PRIVATE_KEY_PASSWORD Private key password
JWT_SECRET JWT secret
JWT_ISSUER JWT issuer
JWT_AUDIENCE JWT audience
CORS_ALLOWED_HOSTS Comma-separated allowed CORS hosts
CORS_ALLOW_ANY_HOST Allow any CORS host (true/false)
APK_STORAGE_ROOT Root dir for APK + screenshot files

Client Configuration (Connect Android to Server)

  • Android Emulator: Works out-of-the-box. The app defaults to http://10.0.2.2:8083 (the special alias for your host machine's localhost).
  • Physical Device:
    1. Tap the Settings (gear icon) in the Android app.
    2. Update Host URL to match your server's LAN IP (e.g., http://192.168.1.50:8083).
    3. Note: If using HTTP, ensure you are on a trusted network.
  • Web Client (Wasm): Automatically infers the API URL from the browser's address bar. No configuration needed.

Architecture & Design

High-level Overview

ApkDepot follows a clean separation between the Ktor backend and the client applications (Android & Wasm), sharing significant business logic and UI code.

Context (System View): C4 Context Diagram

🛠️ Detailed Architecture Diagrams (Containers, Components, Sequences)

Containers: C4 Container Diagram

Server Components: Server Components

Shared Components: Shared Module Components

Flow: Upload Release: Admin Upload Sequence

Flow: Android Update: Android Update Sequence

Tech stack

This project showcases a production-style Kotlin Multiplatform setup:

Component Technology Highlights
Common logic Compose / Kotlin Multiplatform Shared ViewModels, repositories, DTOs, networking, navigation.
Android client Jetpack Compose Shares most UI with Web. Uses PackageManager for install/update state.
Web client Compose Multiplatform for Web (Wasm) Same UI as Android; provides download flows.
Backend Ktor API + SQLite + serves bundled Wasm SPA.
Database SQLite + SQLDelight Type-safe DB access.
DI & state Koin + Coroutines DI with StateFlow-driven MVVM.

Key engineering decisions

  • Unified deployment: The Wasm frontend is bundled into the Ktor fat JAR (custom Gradle tasks).
  • “Smart” configuration:
    • Web: infers API base URL from window.origin.
    • Android: persists server configuration.
  • Storage split: binaries + screenshots on filesystem (easy backup), metadata in SQLite.
  • Platform-specific actions: Shared PublicActionButtons UI renders Download on Web and * Install/Update* on Android.

Limitations & trade-offs

  • HTTP & Clipboard: browsers block the Clipboard API on insecure contexts (HTTP). This affects token copy on LAN.
    • Workaround: use localhost or enable HTTPS.
  • Polling vs push: to avoid external dependencies, the Android client polls for updates when opened (no background push notifications).
  • Wasm maturity: Kotlin/Wasm is still evolving; it requires a modern browser.
  • Single admin: intended for a single-admin setup (no multi-user permission system).

Enabling HTTPS (required for copy/paste on LAN)

To enable the Clipboard API on other devices in your network, serve over HTTPS.

Recommended: use a local dev CA (trusted on your phone)

This generates:

  • a local Certificate Authority (CA) you install on your phone once
  • a server certificate signed by that CA
  • a keystore.jks you point Ktor to

From the repo root:

./scripts/generate-dev-https.sh \
  --cn apkdepot.local \
  --san "DNS:apkdepot.local,DNS:localhost,IP:127.0.0.1,IP:192.168.1.10"

Then configure the server (env vars are easiest):

export SSL_KEYSTORE_PATH=.tls/keystore.jks
export SSL_KEYSTORE_ALIAS=apkdepot
export SSL_KEYSTORE_PASSWORD=changeit
export SSL_PRIVATE_KEY_PASSWORD=changeit

Install the CA certificate on Android:

  • Copy .tls/ca/ca.cer (recommended) or .tls/ca/ca.pem to your phone.
  • Install it as a CA certificate (Settings | Security | Encryption & credentials | Install a certificate | CA certificate).
  • If your device asks for confirmation/warnings: that's expected for user-added CAs.

Important:

  • Your browser URL must match the certificate SAN (hostname/IP). If you browse via https://192.168.1.10:8443, make sure that IP is in --san.
  • Keep tls/ca/ca.key secret.

Security note (local network defaults)

The Android client is configured for local/LAN use by default, which means it currently allows cleartext HTTP traffic to make home-server setup frictionless.

If you’re using ApkDepot outside a trusted network (or you simply prefer stricter defaults), it’s a good idea to:

That file controls whether the app allows cleartext traffic and which certificate authorities it trusts.


Contributing

This project is in Beta/MVP status. PRs and issue reports are welcome.

Development commands:

  • Run server + web: ./gradlew :server:runFullStack
  • Run Android app: ./gradlew :composeApp:installDebug
  • Lint: ./gradlew ktlintCheck

License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

About

ApkDepot is a "Simplified Android App Store" for your home server. It allows developers and enthusiasts to host, manage, and distribute their own Android APP collections or internal family apps

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages