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

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages