ARCHIVE

Archive

More Projects

The Salko Oblak web gallery 01

2026 → present · solo

Salko Oblak · A Self hosted cloud

A self-hosted photo and video cloud running on a PC in my flat: a gallery in the browser, an Android app that uploads the camera roll by itself, two desktop apps, an admin panel, and share links for people with no account.

Languages
TypeScript · Kotlin · Rust · JavaScript · SQL
Tools
Node.js · Express · SQLite · Jetpack Compose · Tauri · ONNX Runtime · CLIP · sharp · ffmpeg · Caddy
Runs on
Linux · Windows · Android

Self-hosted · Solo build

01Waht is project made of

The web app: photos grouped by date, with the year scrubber on the right.
Web appThe gallery in a browser. Photos grouped by date, year scrubber on the right, Drive and albums in the sidebar.

Server

API, storage logic and host for the web app.

Web app

Gallery in a browser: photos, videos, a Drive for everything else, shared albums.

Android app

The same as web app, plus the camera roll uploading itself in the background.

Desktop client

Gallery in a native window, plus watched folders that upload themselves.

Desktop server

Runs the cloud on the host PC: a toggle, the settings, a button to the admin panel.

Admin panel

Approve devices, create users, set quotas, publish an Android update. Host machine only.

02The stack

Server
  • TypeScript
  • Express 5
  • SQLite · WAL
  • sharp
  • ffmpeg
  • JWT
  • bcrypt
Web app
  • JavaScript
  • Plain ES modules
  • esbuild
Android
  • Kotlin
  • Jetpack Compose
  • OkHttp
  • WorkManager
  • Android
Desktop
  • Rust
  • Tauri 2
  • Windows
  • Linux
Search
  • ONNX Runtime
  • CLIP ViT-B/32
  • YuNet
  • SFace
Delivery
  • Caddy
  • NSIS
  • deb / AppImage
  • cargo-xwin
  • Gradle

03Security

A brand-new device can do exactly two things: ask to be approved, and poll for the verdict. Everything else answers 403 until Admin approves it by hand in the admin panel.

Admin panel, Security events: a table of blocked devices with IP, client and the path they asked for.
Admin · securityEvery rejected device, with the client that sent it and the path it wanted. Mostly bots probing /api/.env.
  1. The gate is server-side. The Android client locks itself too, with an OkHttp interceptor, but a patched client still gets 403 on every path.
  2. Flooding is bounded three ways: ten requests per IP per quarter hour, three pending per IP, two hundred pending in total.
  3. Login compares a bcrypt hash at cost 12 and returns a signed token; ten attempts per quarter hour per IP-and-username.
  4. Every query filters by owner, and every path resolves through one function that re-checks it stayed inside its storage root.
  5. Thumbnails load as ordinary <img> tags via an httpOnly, SameSite=Strict cookie, so the bearer token stays in memory and nowhere else.

04The Android gallery uploads itself

The Android app checks for new pictures in the background and marks each photo as it lands. Anything that is not a photo or a video goes to Drive.

Android app, Pictures: the camera roll by day with a synced count per day.
Android app, Drive: a 112 MB zip uploading, with pause and cancel.
  1. The quota is checked before the request body is read.
  2. The file is opened, not trusted: sharp decodes images, ffmpeg probes video.
  3. It lands on disk as a UUID plus a sanitised extension; the original name stays a text column in the database.
  4. Derivatives — thumbnail, poster frame, short preview clip — are generated behind a concurrency gate.
  5. Both sides compute a SHA-256 and compare.
  6. Large files go up in 8 MiB chunks written straight to disk at their offset. A retry gets a 409 carrying the offset the server actually has.

05Search that knows what a cat looks like

The server indexes what the photographs show, not what they are called: a CLIP vision tower turns every photo into a 512-dimension vector. Not one of these files has cat in its name, and the same query typed in Slovenian or Croatian returns the same photos.

The web app searching for “white cat”, returning photos of a white cat across several years.
Web app · search“white cat”, across five years of photos, ranked by how well the picture matches the sentence.
  • Indexing claims work by absence. Any photo with no vector row is a photo still to do, so a restart picks up where it stopped.
  • Vectors live in memory and are scanned linearly. 50 000 photos is about 50 MB and 20 ms.
  • The cut is relative, not a threshold. Results are kept by standard deviations above the library's own mean for that query.
  • Faces are found, grouped, and left anonymous until somebody answers the “who is this?” card.

06Desktop folder watcher

The desktop app builds its frontend from the repository's existing web build, so the gallery is not reimplemented. What it adds on top is folder watching, which a browser cannot do.

The desktop sync app: an empty queue and 10 453 uploaded files, next to the terminal running the server.
Desktop · folder syncQueue empty, 10 453 files uploaded, the device approved, running next to the terminal that started the server.
  • Upload only. Delete events are ignored, so removing a file from the PC never removes it from the cloud.
  • Never half a file. A path is left alone until its size and mtime have held still for three seconds.
  • Never twice. A ledger of path, size, mtime and SHA-256 recognises a moved or renamed copy by content.
  • Failures retry, then stop. Network errors and 5xx retry three times with backoff.

07Shipping in a simple form factor

One command builds the handover folder: a Windows side and a Linux side, each with the server installer, the gallery installer and a plain-language READ ME FIRST.

The server app before setup: status Stopped, with a Start button.
01 · StoppedFirst open gives you one button.
The server app running, showing the local address of the gallery.
02 · RunningFour seconds later, with the address to open.
Server settings: admin username and password, and the folder where files are kept.
03 · SettingsAdmin account and where the files live.
Advanced settings: listen address, ports, allowed web origins and the sign-in secret.
04 · AdvancedPorts, allowed origins, and a sign-in secret that logs every device out when rotated.
  • The staging script downloads a Node runtime and verifies its checksum.
  • SQLite, sharp and ffmpeg are native code, so each installer is built on the OS it targets.
  • Windows installers cross-build on Linux via cargo-xwin.
  • New Android builds are published from the admin panel. Android notices and prompts for update in settings.
Triad of Redemption title screen 02

Triad of Redemption

2D action RPG in Java with LibGDX. Project lead of a 14-person team.

Languages
Java
Tools
LibGDX · Gradle · Tiled · Git
Engine & structure

Levels are made in Tiled and loaded as tilemaps, where walls, traps, bridges, end zones and map triggers all come straight from the map's object layers, so a new level needs no new code.

Combat & AI

Sprite animation driven by a state machine fed from a texture atlas. An attack spawns a directional hitbox that registers each enemy only once per swing.

Riddle boss & mini games

The final boss has no combat, just timed modal minigames.

SOL Novice news aggregator interface 03

SOL Novice · Web News Aggregator

Slovenian news aggregator and archive. Grew out of several small school projects around one theme.

Languages
JavaScript · TypeScript · Python · Kotlin
Tools
Node.js · React · MongoDB · Leaflet
Runs on
Android
Server

REST API with full CRUD and a multi-source scraper. MongoDB Atlas via Mongoose collections. The Node server shells out to Python for image-based news search.

Web app

Main page shows scraped news with filtering. Leaflet map places articles geographically. Statistics tab with multiple Nivo charts for news analytics.

Novinar · Android

Android app for viewing and publishing news, with an interactive map and markers pinned by article location.

Desktop clientC++ · Qt Widgets · console mode 04

SOL Sensors

Shows current system resource usage. Written in C++, with a Qt GUI and a console mode.

Languages
C++
Tools
Qt · Git
GitHub
3D RPG gameplay screenshot in Unreal Engine 4 05

3D RPG · UE4

Small 3D RPG built in Unreal Engine 4 as a high school final project.

Mostly Blueprints, with some logic written directly in C++.

Languages
C++
Tools
UE4 Blueprints

Unreal Engine 4 · 3D