From 4306e60778f5c728f26ebbf2e4cb69826789a258 Mon Sep 17 00:00:00 2001 From: Stavros Date: Thu, 5 Dec 2024 17:43:18 +0200 Subject: [PATCH] feat: smallweb app (#5824) * feat: smallweb app * refactor: use env vars for config * chore: bump version * fix: change data path --- apps/smallweb/config.json | 20 ++++++++++++ apps/smallweb/data/www/index.md | 3 ++ apps/smallweb/docker-compose.yml | 42 ++++++++++++++++++++++++++ apps/smallweb/metadata/description.md | 38 +++++++++++++++++++++++ apps/smallweb/metadata/logo.jpg | Bin 0 -> 13863 bytes 5 files changed, 103 insertions(+) create mode 100644 apps/smallweb/config.json create mode 100644 apps/smallweb/data/www/index.md create mode 100644 apps/smallweb/docker-compose.yml create mode 100644 apps/smallweb/metadata/description.md create mode 100644 apps/smallweb/metadata/logo.jpg diff --git a/apps/smallweb/config.json b/apps/smallweb/config.json new file mode 100644 index 0000000000..79dee1a59a --- /dev/null +++ b/apps/smallweb/config.json @@ -0,0 +1,20 @@ +{ + "$schema": "../schema.json", + "name": "Smallweb", + "id": "smallweb", + "available": true, + "short_desc": "Your internet folder", + "author": "pomdtr", + "port": 8367, + "categories": ["development"], + "description": "A self-hostable personal cloud inspired by serverless platforms and cgi-bin", + "tipi_version": 1, + "version": "0.17.14", + "source": "https://github.com/pomdtr/smallweb", + "website": "https://smallweb.run", + "exposable": true, + "supported_architectures": ["arm64", "amd64"], + "form_fields": [], + "created_at": 1724134338800, + "updated_at": 1724134338800 +} diff --git a/apps/smallweb/data/www/index.md b/apps/smallweb/data/www/index.md new file mode 100644 index 0000000000..70a930c2ab --- /dev/null +++ b/apps/smallweb/data/www/index.md @@ -0,0 +1,3 @@ +# Hello, world! + +Smallweb is running! \ No newline at end of file diff --git a/apps/smallweb/docker-compose.yml b/apps/smallweb/docker-compose.yml new file mode 100644 index 0000000000..864a2c1ac7 --- /dev/null +++ b/apps/smallweb/docker-compose.yml @@ -0,0 +1,42 @@ +services: + smallweb: + container_name: smallweb + image: ghcr.io/pomdtr/smallweb:0.17.14 + restart: unless-stopped + user: 0:0 + environment: + - SMALLWEB_DOMAIN=${APP_DOMAIN} + - SMALLWEB_CUSTOM_DOMAINS=smallweb.${LOCAL_DOMAIN}=*; + volumes: + - ${APP_DATA_DIR}/data/:/smallweb + networks: + - tipi_main_network + labels: + # Main + traefik.enable: true + traefik.http.middlewares.smallweb-web-redirect.redirectscheme.scheme: https + traefik.http.services.smallweb.loadbalancer.server.port: 7777 + # Web + traefik.http.routers.smallweb-insecure.rule: Host(`${APP_DOMAIN}`) || HostRegexp(`.+\.${APP_DOMAIN}`) + traefik.http.routers.smallweb-insecure.entrypoints: web + traefik.http.routers.smallweb-insecure.service: smallweb + traefik.http.routers.smallweb-insecure.middlewares: smallweb-web-redirect + # Websecure + traefik.http.routers.smallweb.rule: Host(`${APP_DOMAIN}`) || HostRegexp(`.+\.${APP_DOMAIN}`) + traefik.http.routers.smallweb.entrypoints: websecure + traefik.http.routers.smallweb.service: smallweb + traefik.http.routers.smallweb.tls.certresolver: myresolver + traefik.http.routers.smallweb.tls.domains[0].main: ${APP_DOMAIN} + traefik.http.routers.smallweb.tls.domains[0].sans: "*.${APP_DOMAIN}" + # Local domain + traefik.http.routers.smallweb-local-insecure.rule: Host(`smallweb.${LOCAL_DOMAIN}`) || HostRegexp(`.+\.smallweb\.${LOCAL_DOMAIN}`) + traefik.http.routers.smallweb-local-insecure.entrypoints: web + traefik.http.routers.smallweb-local-insecure.service: smallweb + traefik.http.routers.smallweb-local-insecure.middlewares: smallweb-web-redirect + # Local domain secure + traefik.http.routers.smallweb-local.rule: Host(`smallweb.${LOCAL_DOMAIN}`) || HostRegexp(`.+\.smallweb\.${LOCAL_DOMAIN}`) + traefik.http.routers.smallweb-local.entrypoints: websecure + traefik.http.routers.smallweb-local.service: smallweb + traefik.http.routers.smallweb-local.tls: true + # Runtipi managed + runtipi.managed: true \ No newline at end of file diff --git a/apps/smallweb/metadata/description.md b/apps/smallweb/metadata/description.md new file mode 100644 index 0000000000..7f2e48699b --- /dev/null +++ b/apps/smallweb/metadata/description.md @@ -0,0 +1,38 @@ +# Smallweb - Host websites from your internet folder + +> Warning ⚠️: The app needs to be accessed by local domain or domain in order to work, accessing via port will ***not*** work. + +Smallweb is a lightweight web server based on [Deno](https://deno.com). It draws inspiration from both legacy specifications like [CGI](https://en.wikipedia.org/wiki/Common_Gateway_Interface), modern serverless platforms such as [Val Town](https://val.town) and static sites generators like [Blot.im](https://blot.im). + +Smallweb maps domains to folders in your filesystem. For example, if you own the `pomdtr.me` domain: + +- `https://www.pomdtr.me` maps to `~/smallweb/www` +- `https://example.pomdtr.me` maps to `~/smallweb/example` + +Creating a new website is as simple as creating a folder and opening the corresponding URL in your browser. There's no need to configure a build step (unless you want to) or start a development server. Since servers are mapped to folders, you can manage them using standard Unix tools like `cp`, `mv`, or `rm`. + +## A self-hosted personal cloud + +Each incoming HTTP request is sandboxed in a single Deno subprocess by the Smallweb evaluation server. If there are no incoming requests, no resources are used, making it an ideal solution for low-traffic websites. + +Smallweb does not use Docker, but it still sandboxes your code using Deno. And if you website suddenly go viral, you can move your site to Deno Deploy in one command. + +## Installation + +All the instructions are available in the [docs](https://docs.smallweb.run). + +## Examples + +All the websites on the `smallweb.run` domain are hosted using smallweb (including this one): + +- +- +- + +Since creating smallweb websites is so easy, you can even create super simple ones. For example, when I want to invite someone to the smallweb discord server, I just send him the link , which maps to `~/smallweb/discord/main.ts` on my vps. + +```ts +export default { + fetch: () => Response.redirect("https://discord.gg/BsgQK42qZe"), +}; +``` \ No newline at end of file diff --git a/apps/smallweb/metadata/logo.jpg b/apps/smallweb/metadata/logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cd1a530789f823c7a390289a7d1077a7e48a2255 GIT binary patch literal 13863 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 U3TzK2QPsWyrv)p8XZxT3524uAqW}N^ literal 0 HcmV?d00001