{"id":15047611,"url":"https://github.com/ungive/loon","last_synced_at":"2025-04-10T01:05:57.604Z","repository":{"id":244828514,"uuid":"816341127","full_name":"ungive/loon","owner":"ungive","description":"Make a local file accessible online instantly via HTTP","archived":false,"fork":false,"pushed_at":"2025-02-24T17:02:04.000Z","size":2258,"stargazers_count":11,"open_issues_count":5,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-10T01:05:48.833Z","etag":null,"topics":["cpp","cpp17","go","golang","libhv","protobuf","reverse-tunnel","tunnel"],"latest_commit_sha":null,"homepage":"https://ungive.github.io/loon/","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ungive.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"ungive","liberapay":"jonasvandenberg"}},"created_at":"2024-06-17T14:43:52.000Z","updated_at":"2025-04-04T12:30:08.000Z","dependencies_parsed_at":"2024-06-27T18:40:12.189Z","dependency_job_id":"fcb21e0b-f66d-47da-a945-348a8707d50e","html_url":"https://github.com/ungive/loon","commit_stats":null,"previous_names":["ungive/loon"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ungive%2Floon","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ungive%2Floon/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ungive%2Floon/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ungive%2Floon/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ungive","download_url":"https://codeload.github.com/ungive/loon/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248137888,"owners_count":21053775,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cpp","cpp17","go","golang","libhv","protobuf","reverse-tunnel","tunnel"],"created_at":"2024-09-24T21:01:16.667Z","updated_at":"2025-04-10T01:05:57.580Z","avatar_url":"https://github.com/ungive.png","language":"C++","readme":"\u003e Note: The project is still a WIP,\n\u003e a license and proper versioning will be added soon.\n\n# local files online \u0026ndash; loon\n\n![loon logo](./assets/loon-small.png)\n\n**\u003cins\u003eloon\u003c/ins\u003e** allows you to make\na **\u003cins\u003elo\u003c/ins\u003ecal** file\naccessible **\u003cins\u003eon\u003c/ins\u003eline**.\n\nThe loon client generates one URL per resource,\nwhich points to an HTTP server,\nwhich in turn forwards any request back to the loon client\nthrough a websocket connection.\nIt acts like a **tunnel for HTTP traffic**,\nbut with a very minimal and lightweight implementation behind it,\nideal to keep your client small and lean.\n\nThe Go server and C++ client implementation are battle-tested\nand actively used with [**Music Presence**](https://musicpresence.app),\na desktop application to show what you are listening to\nin your Discord status!\n\n## Architecture overview\n\nThere are three main actors with loon:\n\n**Client** \u0026ndash; The client library is used on the device\nthat wishes to share a resource.\nIt connects to the server via a websocket connection\nand remains connected for the duration it wants to share these resources.\nSharing a resource is as simple as registering it with the client\nand generating a URL - this process is instant.\nThe URL can then be shared with a third party to access the resource.\nUploading is done when the server receives a request at the generated URL,\nthe request is forwarded to the client and then uploaded by the client\nthrough the websocket connection.\nThe generated URL becomes invalid once the resource has been unregistered\nor the websocket connection has been closed.\n\n**Server** \u0026ndash; The server accepts both client websocket connections\nand requests to URLs that were generated by these clients.\nIts sole purpose is to provide a mechanism for clients\nto generate URLs that are accessible from the internet\nand to forward requests and responses between clients and third parties.\n\n**Third party** \u0026ndash; Any third party can make requests to generated URLs\nto access the resource that has been registered by a client.\n\n## Server limitations\n\nThe loon server in this repository\nis **meant to be put behind a reverse proxy and a cache**.\nThe only caching mechanism that is provided by the implementation\nis control over the `max-age` value in a response's `Cache-Control` header,\nbut the actual caching itself needs to be taken care of separately.\n\nThe used cache and/or reverse proxy must guarantee the following\nto prevent clients from being flooded with requests:\n\n- Concurrent requests **MUST** lead to a *single request* to the client,\n  whose response should then be cached and served.\n- Any `Cache-Control` request header **MUST** be ignored,\n  otherwise third parties can bypass the cache,\n  e.g. by setting the `no-cache` directive.\n  When using a reverse proxy, this header should be deleted from all requests.\n  If not done properly, the cache is effectively rendered obsolete\n  from an attackers point of view.\n- Query parameters are not forwarded to a connected client,\n  so they **MUST** not be part of the cache key.\n  Setting a meaningless query parameter should not bypass the cache.\n\nA deployment example can be found in [**deployments**](./deployments).\n\n## What is in this repository?\n\n- System and protocol specification: **[api](./api)**\n- A well-tested server library written in Go: **[pkg/server](./pkg/server)**\n- A simple client library written in Go: **[pkg/client](./pkg/client)**\n- A CLI program to run the server and client: **[cmd/loon](./cmd/loon)**\n  - It makes use of the above server and client library\n- A feature-complete, reference client library written in C++:\n  **[client](./client)**\n  - Well-tested and has more features than the Go client library\n  - Dependencies: libhv or Qt for websocket communication + OpenSSL + Protobuf\n  - Compatible with C++17 or newer\n  - Uses the CMake build system\n  - Small footprint: 2.899 lines for commit `6d44d2`\n    (excluding websocket abstraction and tests)\n  - Documentation: *https://ungive.github.io/loon/client*\n- A ready to use server Docker image:\n  **[build/package/Dockerfile](./build/package/Dockerfile)**\n- A server deployment example with Docker Compose and Caddy:\n  **[deployments](./deployments)**  \n  For a more production-ready example have a look at\n  **[music-presence/client-proxy](https://github.com/music-presence/client-proxy)**\n- An example server configuration to quickly get started:\n  **[examples/server/config.yaml](./examples/server/config.yaml)**\n\n\u003e **Warning!**  \n\u003e The public API of these libraries and programs\n\u003e is not yet stable and might change in the future.\n\u003e Wait for the first stable release, if a stable API is required.\n\n## Basic usage\n\n### Run the server\n\nTo expose the server to your local network,\nchange the value after `-addr` to `:8080`\nand edit `base_url` in the used example configuration\nto contain your computer's IP address and the configured port,\ne.g. `http://192.168.178.2:8080`.\n\n#### Using Go\n\n```sh\ngit clone https://github.com/ungive/loon\ncd ./loon\ngo install ./cmd/loon\nloon server -addr localhost:8080 -config examples/server/config.yaml\n```\n\n#### Using Docker\n\nBuild the image yourself:\n\n```sh\ndocker build -t loon -f build/package/Dockerfile .\ndocker run --rm -it -v $(pwd)/examples/server/config.yaml:/app/config.yaml -p 8080:80 ungive/loon:latest\nloon client -server http://localhost:8080 assets/loon-small.png\n```\n\nOr use a pre-built image from the GitHub Container Registry\n(not updated regularly yet):\n\n```sh\ndocker run --rm -it -v $(pwd)/examples/server/config.yaml:/app/config.yaml -p 8080:80 ghcr.io/ungive/loon:latest\n```\n\n### Run the client\n\n```sh\nloon client -server http://localhost:8080 assets/loon-small.png\nloon client -server http://192.168.178.43:8080 assets/loon-small.png assets/loon-full.png\n```\n\nExample output:\n\n```\nassets/loon-small.png: http://localhost:8080/BnRWzVodwVmY11V7MXQ-Mw/f7AapwFxVSwlHgiTungSpqFNkb_jAdkhvGVGaNeoWJc/loon-small.png\n```\n\nOpen the URL in a browser and it should show the file's contents.\n\n## Server deployment\n\nFor a docker compose deployment example, see [`deployments`](./deployments).\nFeatures:\n\n- Uses Caddy server (similar to nginx)\n- Client responses are cached (using Souin cache)\n- Prometheus and Grafana integration for metrics\n- The websocket endpoint is authenticated,\n  so only trusted clients can connect to the server\n- Uses a self-signed certificate for websocket connections,\n  so clients can authenticate the server\n- Generated URLs are exposed via HTTPS with a certificate from a trusted CA\n  (thanks to Caddy), so traffic is encrypted everywhere\n\n## What problem does this solve?\n\nTo better understand the aim of this software\nand what problem it tries to solve,\nread the following problem statement:\n\nYou want a **local file** on your computer\nto be **publicly accessible from the internet**\nby generating **a simple HTTP(S) URL** for it,\nwhich points to a well-known server\nthat acts as the middleman for file transfers.\nFiles should only be uploaded\nwhen they are actually requested via that URL\nand they should not be stored on the server indefinitely.\n\nThis should be possible without:\n- exposing your local computer to the internet,\n- having to start an HTTP server and binding to a port on the local device,\n- having to use sophisticated tunneling software,\n  which does more than you actually want it to,\n- or using some kind of uploading service or cloud provider.\n\nAt the same time you want to have guaranteed protection from abuse:\n- multiple requests to the same generated URL\n  should use an optionally cached response on the server,\n  so that you only have to upload your file once,\n  within a given period of time.\n- simultaneous requests to the same URL\n  should only require you to upload once, not multiple times.\n- requests to invalid URLs (not generated by you)\n  should be ignored by the server.\n\nFile contents do not need to be end-to-end encrypted,\nit is okay for any third party to see what it is in the file,\nincluding the intermediate server and anyone in possession of a generated URL.\n\n## The solution\n\nThe proposed solution is a server that acts as a middleman,\nwhich accepts requests to a well-known HTTPS endpoint\nand forwards them to a client through a websocket connection.\nThe client then sends the image data back through that connection,\nwhich is then sent to whoever made the request.\n\nThe protocol is described in full detail in\n**[api/specification.md](./api/specification.md)**.\n\n## Use cases\n\n### Discord Rich Presence\n\nThis software was primarily designed for the use case of\nhosting an image online, that is only available locally,\nfor a temporary amount of time,\nso that the image can be used within a Discord activity status\n(also known as Discord Rich Presence),\nby supplying the generated HTTPS URL to the Discord Game SDK,\nas the `largeImageKey` or `smallImageKey`.\n\nDiscord requires the image shown in a status\nto either be one of a handful of previously uploaded images,\nwhich is insufficient for this use case,\nor a link to an image that is available via HTTPS.\nSince the image is only available locally, it must be uploaded to a server\nor a server on the client computer must be exposed to the internet.\nThe latter was not an option, so a solution was needed\nto be able to temporarily upload images,\nbut with the added benfit that:\n- images are not stored indefinitely (like with an image uploading service)\n- the image is only uploaded anywhere when it is actually needed\n  (sometimes a Discord status is never actually viewed by anybody)\n- the client merely has to connect to a websocket server\n  and adhere to a very simple communication protocol\n\nThis software solves that problem,\nby allowing any client to generate URLs locally,\nset them as an image in the Discord status\nand supply the image contents for the case that the image is needed\nvia a websocket connection.\n\n### Other use cases\n\nSince files are not end-to-end encrypted\nthe number of other use cases are limited,\nbut it could certainly be used for other things as well.\nIf you have any ideas or if you are using this software for another use case,\nfeel free to open an issue to bring it to my attention!\n\n## License\n\nProject is still a WIP, a license will be added soon!\n\n## Copyright\n\nCopyright (c) 2024 Jonas van den Berg\n","funding_links":["https://github.com/sponsors/ungive","https://liberapay.com/jonasvandenberg"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fungive%2Floon","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fungive%2Floon","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fungive%2Floon/lists"}