From 10e2b7c7ed5fc4407dab1cca9b689a1da791d680 Mon Sep 17 00:00:00 2001 From: reachableceo Date: Tue, 1 Sep 2026 19:39:53 -0500 Subject: [PATCH] feat: add FX Cloudron package (DevOps-Tools) [#640] FX 0.9.48 ("poor man's function as a service") as the 13th package: pre-compiled goreleaser release binary with sha256 pin on cloudron/base:4.0.0. fx is a CLI with no user concept, so the package is a workstation: pinned binary + persistent workspace (functions/ssh/kube) driven from the Cloudron web terminal, with an auth-proxied landing page as the only HTTP surface (httpAuth proxy). Deploys target remote Docker hosts over key-based SSH or Kubernetes via FX_KUBECONF. Build green; runtime smoke (landing page, workspace seeding, fx -v) green. Docs gardened to 13 packages. Ticket: https://projects.knownelement.com/issues/640 --- JOURNAL.md | 72 ++++++++++++- .../DevOps-Tools/fx/.dockerignore | 1 + .../DevOps-Tools/fx/.env.example | 14 +++ .../DevOps-Tools/fx/CHANGELOG.md | 25 +++++ .../DevOps-Tools/fx/CloudronManifest.json | 27 +++++ Package-Workspace/DevOps-Tools/fx/Dockerfile | 60 +++++++++++ Package-Workspace/DevOps-Tools/fx/README.md | 101 ++++++++++++++++++ Package-Workspace/DevOps-Tools/fx/logo.png | Bin 0 -> 5984 bytes Package-Workspace/DevOps-Tools/fx/start.sh | 40 +++++++ Package-Workspace/DevOps-Tools/fx/status.html | 58 ++++++++++ README.md | 11 +- STATUS.md | 16 +-- 12 files changed, 412 insertions(+), 13 deletions(-) create mode 100644 Package-Workspace/DevOps-Tools/fx/.dockerignore create mode 100644 Package-Workspace/DevOps-Tools/fx/.env.example create mode 100644 Package-Workspace/DevOps-Tools/fx/CHANGELOG.md create mode 100644 Package-Workspace/DevOps-Tools/fx/CloudronManifest.json create mode 100644 Package-Workspace/DevOps-Tools/fx/Dockerfile create mode 100644 Package-Workspace/DevOps-Tools/fx/README.md create mode 100644 Package-Workspace/DevOps-Tools/fx/logo.png create mode 100755 Package-Workspace/DevOps-Tools/fx/start.sh create mode 100644 Package-Workspace/DevOps-Tools/fx/status.html diff --git a/JOURNAL.md b/JOURNAL.md index e1cf981..99641f6 100644 --- a/JOURNAL.md +++ b/JOURNAL.md @@ -4,7 +4,7 @@ **Project**: TSYSDevStack-SupportStack-Cloudron **Goal**: Package ~57 applications for Cloudron PaaS platform **Start Date**: 2025-01-24 -**Current Status**: 12/~57 packages completed (~21%) +**Current Status**: 13/~57 packages completed (~23%) ## Completed Packages @@ -966,6 +966,76 @@ PostgreSQL databases (OIDC login, OPA-authorized queries, cached results) **Commit**: `feat: add Database-Gateway Cloudron package (Infrastructure) [#639]` +### 13. FX (DevOps-Tools) ✅ +**Date**: 2026-09-01 +**Application**: FX — "poor man's function as a service" (metrue/fx): a CLI +that turns a stateless function file (JS, Python, Go, Ruby, Java, PHP, +Perl, Crystal, Rust, Julia, D) into a running HTTP service on your own +Docker host or Kubernetes cluster +**Package Size**: 3.55GB (cloudron/base 4.0.0 dominates) +**Ports**: 8000 (HTTP landing/health page only — fx itself listens on +nothing) +**Addons**: localstorage (auth proxy, no database) + +**Key Learnings**: +- **Auth gate verdict**: NO user concept — fx is a terminal tool: no UI, + no accounts, no SSO hooks → `httpAuth.type = proxy` gates the only + HTTP surface (the landing page), the Rathole/Easy-Gate pattern for + user-less apps. Terminal + workspace access is Cloudron's app access + list; the SSH keys under /app/data/ssh are target credentials +- **First pure "CLI workstation" package**: fx has no daemon, so the + landing-page server (python3 -m http.server) is the ONLY long-running + process; the fx binary runs on demand from the Cloudron web terminal. + Package value = pinned binary + persistent workspace + (/app/data/{functions,ssh,kube}) +- **No Docker daemon in Cloudron apps** (and no host socket access): + fx's local-docker mode is unusable in-app; deploys target remote + Docker hosts over SSH (Go-native ssh library, key-based — no + openssh binary needed in the image) or Kubernetes via FX_KUBECONF +- **Release pinning on a quiet upstream**: last published release is + 0.9.48-alpha.d91a7a0 (2021-06-10) while master sits at 2023-10-24; + packaged the release (what official scripts/install.sh installs; the + binary self-reports 0.9.48), not master +- **glibc trap, Rathole side**: the goreleaser `Tux` asset is + glibc-built → Ubuntu base (cloudron/base:4.0.0); the mirror image of + Database Gateway's musl/alpine pairing +- **Starter functions must match upstream shapes exactly**: JS = + Koa-style `(ctx) => { ctx.body = ... }`, Python = plain + `def fx(request)` — copied from upstream examples verbatim + +**Build Process**: +- Pre-compiled-binaries pattern (JOURNAL pattern #5): release tarball + `fx_0.9.48-alpha.d91a7a0_Tux_64-bit.tar.gz` downloaded in-Dockerfile + behind a sha256 pin taken from the upstream checksums.txt; `fx -v` + runs inside the build as an executability gate +- Logo: 256x256 "fx" monogram generated in a throwaway alpine:3.20 + container (upstream ships no logo asset) +- start.sh: seeds the /app/data workspace (functions/ssh/kube + two + starter functions) then execs the landing-page server in the + foreground — committed executable + +**Validation**: +- `docker build --cgroup-parent ukrrs-batch.slice -t fx-cloudron:test` + → green (sha256 gate OK, `fx version 0.9.48` printed in-build) +- Runtime smoke: container up, `GET /` → 200 with the FX landing page, + workspace dirs + hello.js/hello.py seeded, startup banner in logs; + container removed after test + +**Files Created**: +- Dockerfile (pre-compiled binary, sha256-pinned, cloudron/base:4.0.0) +- CloudronManifest.json (manifestVersion 2, httpAuth proxy, httpPort + 8000, localstorage only) +- start.sh (workspace seed + landing-page server, exec) — committed + executable +- status.html (auth-proxied landing/usage page) +- README.md (auth story, terminal workflow, remote/K8s usage) +- CHANGELOG.md +- .env.example (FX_HOST / FX_KUBECONF knobs) +- .dockerignore (excludes the cloned repo/ from the build context) +- logo.png (256x256 monogram) + +**Commit**: `feat: add FX Cloudron package (DevOps-Tools) [#640]` + --- ## Packaging Pattern: Download Pre-Compiled Binaries diff --git a/Package-Workspace/DevOps-Tools/fx/.dockerignore b/Package-Workspace/DevOps-Tools/fx/.dockerignore new file mode 100644 index 0000000..7d37f1a --- /dev/null +++ b/Package-Workspace/DevOps-Tools/fx/.dockerignore @@ -0,0 +1 @@ +repo/ diff --git a/Package-Workspace/DevOps-Tools/fx/.env.example b/Package-Workspace/DevOps-Tools/fx/.env.example new file mode 100644 index 0000000..a5e48d2 --- /dev/null +++ b/Package-Workspace/DevOps-Tools/fx/.env.example @@ -0,0 +1,14 @@ +# FX Cloudron package — environment knobs +# +# Set via App -> Configure -> Environment; they are visible to fx when +# run from the app's web terminal. Not secrets. + +# Default deploy target for fx up/list/down/image: @ of a +# Docker host reachable over SSH. Leave unset to pass --host per +# command. The SSH key itself is a file, not env: put it in +# /app/data/ssh/ and pass --ssh_key (fx authenticates with keys only). +#FX_HOST=deploy@docker.example.com + +# Path to a kubeconfig for Kubernetes deploys (or pass -C per command). +# /app/data/kube/config is the persistent home for it. +#FX_KUBECONF=/app/data/kube/config diff --git a/Package-Workspace/DevOps-Tools/fx/CHANGELOG.md b/Package-Workspace/DevOps-Tools/fx/CHANGELOG.md new file mode 100644 index 0000000..c02afdd --- /dev/null +++ b/Package-Workspace/DevOps-Tools/fx/CHANGELOG.md @@ -0,0 +1,25 @@ +# Changelog — FX Cloudron Package + +## 1.0.0 (2026-09-01) + +Initial Cloudron package for fx 0.9.48 (poor man's function as a +service). + +- Pre-compiled-binaries pattern: upstream goreleaser release + `fx_0.9.48-alpha.d91a7a0_Tux_64-bit.tar.gz` (linux/amd64) downloaded + at build time behind a sha256 pin taken from the upstream + checksums.txt; `fx -v` runs inside the build as an executability gate. +- fx is a CLI with no daemon or UI: the package is an fx workstation — + pinned binary + persistent workspace (`functions/`, `ssh/`, `kube/` + under /app/data, with two upstream-shaped starter functions) — driven + from the Cloudron web terminal. +- No user concept → `httpAuth.type = proxy` gates a static landing/usage + page served on the HTTP port (also the platform health check); the + page server is the only long-running process. +- Deploys target remote Docker hosts over key-based SSH (Go-native SSH, + no openssh binary needed in the image) or Kubernetes via kubeconfig + (`FX_KUBECONF`); documented limitation: the app container has no + Docker daemon, so functions never run inside this app. +- Addons: `localstorage` only (workspace persistence; no database). +- Logo: 256x256 monogram generated for this package (upstream ships no + logo asset). diff --git a/Package-Workspace/DevOps-Tools/fx/CloudronManifest.json b/Package-Workspace/DevOps-Tools/fx/CloudronManifest.json new file mode 100644 index 0000000..5fd8100 --- /dev/null +++ b/Package-Workspace/DevOps-Tools/fx/CloudronManifest.json @@ -0,0 +1,27 @@ +{ + "manifestVersion": 2, + "type": "app", + "id": "io.cloudron.fx", + "title": "FX", + "description": "fx is 'poor man's function as a service': a CLI that turns a stateless function file (JavaScript, Python, Go, Ruby, Java, PHP, Perl, Crystal, Rust, Julia, D) into a running HTTP service on your own Docker host or Kubernetes cluster in seconds. This package is an fx workstation: the pinned fx binary plus a persistent workspace, driven from the Cloudron web terminal. Deploy targets are remote Docker hosts (SSH key auth) or a Kubernetes cluster (kubeconfig); the app container itself has no Docker daemon, so functions always run on their target, not inside this app.", + "author": "metrue", + "website": "https://github.com/metrue/fx", + "documentationUrl": "https://github.com/metrue/fx/blob/master/README.md", + "contactEmail": "cloudron@tsys.dev", + "tagline": "Poor man's Function as a Service — deploy function files from the terminal", + "version": "0.9.48", + "upstreamVersion": "0.9.48", + "healthCheckPath": "/", + "httpPort": 8000, + "httpAuth": { + "type": "proxy" + }, + "memoryLimit": 268435456, + "addons": { + "localstorage": {} + }, + "postInstallMessage": "FX is a CLI, not a web app: there is nothing to click through. Open the app's **web terminal** and run `fx -v`. Drop an SSH key into **/app/data/ssh/** (or a kubeconfig into **/app/data/kube/**), put function sources in **/app/data/functions/** (two starters are already there), then `fx up --host @ --ssh_key /app/data/ssh/id_rsa --name hello /app/data/functions/hello.js`. The landing page on the app URL is an auth-proxied usage guide.", + "mediaLinks": [], + "changelog": "Initial Cloudron package for fx 0.9.48 ('poor man's function as a service'). Pre-compiled upstream release binary with sha256 pin, persistent workspace at /app/data (functions/, ssh/, kube/) with two starter functions, and an auth-proxied landing page on the HTTP port (fx has no daemon or UI of its own). Driven from the Cloudron web terminal; deploys go to remote Docker hosts over SSH or to Kubernetes via kubeconfig.", + "icon": "file://logo.png" +} diff --git a/Package-Workspace/DevOps-Tools/fx/Dockerfile b/Package-Workspace/DevOps-Tools/fx/Dockerfile new file mode 100644 index 0000000..8e55432 --- /dev/null +++ b/Package-Workspace/DevOps-Tools/fx/Dockerfile @@ -0,0 +1,60 @@ +# FX Cloudron Package +# +# fx (metrue/fx) is "poor man's function as a service": a CLI that turns a +# stateless function file (JavaScript, Python, Go, Ruby, Java, PHP, Perl, +# Crystal, Rust, Julia, D) into a running HTTP service on your own Docker +# host or Kubernetes cluster, in seconds. fx is a DRIVER-side tool: it +# packages the function, ships it to the target over SSH (key-based) or +# the K8s API, and the TARGET runs the container. +# +# Cloudron app containers have no Docker daemon, so this package is the +# operator's fx WORKSTATION: the pinned fx binary plus a persistent +# workspace (/app/data/functions, /app/data/ssh, /app/data/kube), driven +# from the Cloudron web terminal. Deploy targets are remote Docker hosts +# (--host user@host --ssh_key ...) or a Kubernetes cluster (FX_KUBECONF +# or -C kubeconf). +# +# Authentication: fx has NO user concept — no web UI, no accounts, no +# SSO hooks — so the auth-gate verdict is the proxy row (AGENTS.md +# matrix). The manifest declares httpAuth.type = proxy: Cloudron's auth +# proxy gates the landing page, the only HTTP surface this app has. +# Access to the terminal/workspace itself is Cloudron's app access list. +# +# Upstream: https://github.com/metrue/fx +# - Release 0.9.48-alpha.d91a7a0 (2021-06-10) is the latest published +# release and what the official scripts/install.sh installs; the +# binary reports version 0.9.48. Repo master (2023) has no newer +# release. +# - Ships goreleaser tarballs; the linux/amd64 asset is +# fx__Tux_64-bit.tar.gz, glibc-built -> needs an Ubuntu base. +FROM cloudron/base:4.0.0 + +ARG FX_TAG=0.9.48-alpha.d91a7a0 +ARG FX_SHA256=1ed8a40f81038ca9e33aa09dba2184448b2efa3ed6373936e9f849f50200910f + +# Pre-compiled-binaries pattern (JOURNAL pattern #5): pinned release +# download behind a sha256 gate (checksum taken from the upstream +# checksums.txt asset). Building the 2021-era codebase (go 1.12 modules +# + packr assets) from source would buy nothing over the official +# release binary. +RUN curl -fsSL -o /tmp/fx.tar.gz \ + "https://github.com/metrue/fx/releases/download/${FX_TAG}/fx_${FX_TAG}_Tux_64-bit.tar.gz" \ + && echo "${FX_SHA256} /tmp/fx.tar.gz" | sha256sum -c - \ + && tar -xzf /tmp/fx.tar.gz -C /usr/local/bin --exclude='*.md' --exclude='LICENSE*' \ + && chmod +x /usr/local/bin/fx \ + && rm -f /tmp/fx.tar.gz \ + && /usr/local/bin/fx -v + +# Static landing page served on the Cloudron HTTP port (platform health +# check + auth-proxied usage guide). fx itself has no daemon, so the +# page server is the only long-running process. start.sh seeds the +# /app/data workspace on first run; it is made executable on the host, +# not at build time (Cloudron gotcha). +COPY status.html /app/code/status/index.html +COPY start.sh /app/start.sh + +WORKDIR /app/data + +EXPOSE 8000 + +CMD ["/bin/bash", "/app/start.sh"] diff --git a/Package-Workspace/DevOps-Tools/fx/README.md b/Package-Workspace/DevOps-Tools/fx/README.md new file mode 100644 index 0000000..901e14f --- /dev/null +++ b/Package-Workspace/DevOps-Tools/fx/README.md @@ -0,0 +1,101 @@ +# FX — Cloudron Package + +[fx](https://github.com/metrue/fx) is "poor man's function as a service": +a CLI that turns a stateless function file (JavaScript, Python, Go, Ruby, +Java, PHP, Perl, Crystal, Rust, Julia, D) into a running HTTP service on +your own Docker host or Kubernetes cluster, in seconds. fx packages the +function, ships it to the target over SSH (key-based) or the K8s API, and +the target runs the container. + +This package is an **fx workstation**: the pinned fx binary plus a +persistent workspace, driven from the Cloudron web terminal. Cloudron app +containers have no Docker daemon (and no access to the host's), so +deploys always target a **remote** Docker host or a Kubernetes cluster — +never the app container itself. + +## Packaging overview + +| Aspect | Choice | +|--------|--------| +| Pattern | Pre-compiled binaries (JOURNAL pattern #5) | +| Base image | `cloudron/base:4.0.0` (Ubuntu 22.04; the goreleaser binary is glibc-built) | +| Upstream binary | `fx_0.9.48-alpha.d91a7a0_Tux_64-bit.tar.gz` (linux/amd64), sha256-pinned in the Dockerfile (checksum from upstream `checksums.txt`) | +| Addons | `localstorage` only (workspace persistence; no database) | +| Auth | No user concept → `httpAuth.type = proxy` (see below) | +| Runtime | `start.sh` seeds the workspace and serves the landing page; fx runs on demand from the terminal | + +Why pre-compiled: the last published release is `0.9.48-alpha.d91a7a0` +(2021-06-10) — the same artifact the official `scripts/install.sh` +installs, and the binary reports version `0.9.48`. Repo master (2023) has +no newer release; building the go 1.12-era, packr-using codebase from +source would buy nothing over the official binary. + +## Authentication (auth gate verdict: proxy) + +- fx has **no user concept**: no web UI, no accounts, no SSO hooks — it + is a terminal tool. +- The manifest declares `httpAuth: {"type": "proxy"}`: Cloudron's auth + proxy gates the landing page, which is the only HTTP surface this app + has. Access to the terminal (and therefore the workspace and SSH keys) + is Cloudron's app access list — restrict the app to the admins who + should wield it. +- The SSH keys stored in `/app/data/ssh/` are the credentials for the + *deploy targets*; guard them with the app's access list and file + permissions. + +## Usage + +1. Open the app's **web terminal** (or `cloudron exec`). +2. Drop an SSH private key into `/app/data/ssh/` (e.g. via the file + manager) for your Docker target, or a kubeconfig into + `/app/data/kube/` for a Kubernetes target. +3. Deploy: + +```bash +fx up --host deploy@example.com --ssh_key /app/data/ssh/id_rsa \ + --name hello /app/data/functions/hello.js + +fx list --host deploy@example.com # deployed services +fx down --host deploy@example.com hello +``` + +Kubernetes: + +```bash +export FX_KUBECONF=/app/data/kube/config +fx up --name hello /app/data/functions/hello.py +``` + +The two seeded starters (`hello.js`, `hello.py`) use the exact upstream +example shapes: a Koa-style `ctx` handler for JS, a plain `fx(request)` +callable for Python. + +Knobs: `FX_HOST` (default deploy target, `@`) and +`FX_KUBECONF` (path to kubeconfig) can be set once via Cloudron's +environment settings instead of per-command flags — see `.env.example`. + +## Ports + +| Port | Type | Purpose | +|------|------|---------| +| 8000 | `httpPort` | Landing/usage page + platform health check (behind Cloudron auth proxy) | + +fx itself listens on nothing; deployed functions listen on their target +host, outside this app. + +## Build & test + +```bash +docker build --cgroup-parent ukrrs-batch.slice -t fx-cloudron:test Package-Workspace/DevOps-Tools/fx/ +docker run --rm --entrypoint /usr/local/bin/fx fx-cloudron:test -v # -> 0.9.48 +``` + +## Files + +- `Dockerfile` — pinned release download + sha256 gate on cloudron/base:4.0.0 +- `CloudronManifest.json` — manifestVersion 2, httpAuth proxy, localstorage only +- `start.sh` — workspace seeding + landing-page server (committed executable) +- `status.html` — auth-proxied landing/usage page +- `.env.example` — FX_HOST / FX_KUBECONF knobs +- `.dockerignore` — excludes the cloned `repo/` from the build context +- `logo.png` — 256x256 monogram (fx ships no logo asset) diff --git a/Package-Workspace/DevOps-Tools/fx/logo.png b/Package-Workspace/DevOps-Tools/fx/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c8896dd18a1b2898a9755d816c3c5c06c15e9727 GIT binary patch literal 5984 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 I)dx@i3xyfwJpcdz literal 0 HcmV?d00001 diff --git a/Package-Workspace/DevOps-Tools/fx/start.sh b/Package-Workspace/DevOps-Tools/fx/start.sh new file mode 100755 index 0000000..5263040 --- /dev/null +++ b/Package-Workspace/DevOps-Tools/fx/start.sh @@ -0,0 +1,40 @@ +#!/bin/bash +set -euo pipefail + +# fx is a CLI, not a daemon: it has nothing to serve. The long-running +# process of this package is therefore the auth-proxied landing page on +# the Cloudron HTTP port (health check + usage guide). The fx binary and +# the persistent workspace below are driven interactively from the +# Cloudron web terminal (App -> terminal, or `cloudron exec`). +# +# Workspace layout seeded on first run: +# /app/data/functions/ function sources (two starter functions) +# /app/data/ssh/ SSH keys for remote Docker targets +# /app/data/kube/ kubeconfig for Kubernetes targets + +mkdir -p /app/data/functions /app/data/ssh /app/data/kube + +# Starter functions, exactly the upstream example shapes (Koa-style ctx +# for JavaScript, plain callable for Python). +if [[ ! -f /app/data/functions/hello.js ]]; then + cat > /app/data/functions/hello.js <<'EOF' +module.exports = (ctx) => { + ctx.body = 'hello world' +} +EOF +fi + +if [[ ! -f /app/data/functions/hello.py ]]; then + cat > /app/data/functions/hello.py <<'EOF' +def fx(request): + return "hello world" +EOF +fi + +echo "fx $(/usr/local/bin/fx -v) ready - open the app's web terminal to use it." +echo "Workspace: /app/data/functions (sources), /app/data/ssh (keys), /app/data/kube (kubeconfig)" +echo "Deploy example: fx up --host @ --ssh_key /app/data/ssh/id_rsa --name hello /app/data/functions/hello.js" + +# Landing page (health check + auth-proxied usage guide) is the only +# long-running process - fx itself runs on demand from the terminal. +exec python3 -m http.server "${CLOUDRON_HTTP_PORT:-8000}" --directory /app/code/status diff --git a/Package-Workspace/DevOps-Tools/fx/status.html b/Package-Workspace/DevOps-Tools/fx/status.html new file mode 100644 index 0000000..f1a0331 --- /dev/null +++ b/Package-Workspace/DevOps-Tools/fx/status.html @@ -0,0 +1,58 @@ + + + + + +FX + + + +
+

FX ▲ fx 0.9.48 ready

+

Poor man's function as a service

+ +

fx is a CLI, not a web app — this page exists so + Cloudron has a health endpoint and you have a landing spot (gated by the + Cloudron auth proxy). You drive fx from the app's web + terminal: open the terminal and run fx -v to confirm, + then deploy function files to your own Docker hosts or Kubernetes + clusters. Cloudron app containers have no Docker daemon, so functions + always run on the target you point fx at, never inside this + app.

+ + + + + + +
PathPurpose
/app/data/functions/Function sources (hello.js + hello.py starters seeded)
/app/data/ssh/SSH keys for remote Docker targets (fx logs in with a key, no passwords)
/app/data/kube/kubeconfig for Kubernetes targets
+ +

Deploy to a remote Docker host (JS example):

+
fx up --host deploy@example.com --ssh_key /app/data/ssh/id_rsa \
+       --name hello /app/data/functions/hello.js
+fx list --host deploy@example.com
+fx down --host deploy@example.com hello
+ +

Deploy to Kubernetes (copy your kubeconfig to + /app/data/kube/config first):

+
export FX_KUBECONF=/app/data/kube/config
+fx up --name hello /app/data/functions/hello.py
+ +

Supported languages: Go, + JavaScript, Python, Ruby, Java, PHP, Perl, Crystal, Rust, Julia, D. + Upstream docs: + github.com/metrue/fx

+
+ + diff --git a/README.md b/README.md index 57413cf..2ce1dec 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,9 @@ The Cloudron component focuses on packaging upstream free/libre/open application ### 📊 Current Progress - **Total Applications**: ~57 (see [GitUrlList.txt](GitUrlList.txt)) -- **Completed Packages**: 12/~57 (~21%) +- **Completed Packages**: 13/~57 (~23%) - **Packaging Templates**: Created ✅ -- **Packages Committed & Pushed**: 12 ✅ +- **Packages Committed & Pushed**: 13 ✅ - **Build Tickets**: 46 filed (#633-#678, umbrella [#632](https://projects.knownelement.com/issues/632), Redmine project 55); grist-core excluded (packaged upstream) @@ -34,6 +34,7 @@ The Cloudron component focuses on packaging upstream free/libre/open application | 10 | Easy-Gate | Infrastructure | 3.18GB | 8080 | localstorage (auth proxy) | ✅ Committed | | 11 | Rathole | Infrastructure | 3.51GB | 8000, 2333, 5200-5299 | localstorage (auth proxy) | ✅ Committed | | 12 | Database Gateway | Infrastructure | 93.7MB | 8080 | localstorage, postgresql | ✅ Committed | +| 13 | FX | DevOps-Tools | 3.55GB | 8000 | localstorage (auth proxy) | ✅ Committed | ### 📦 Packages in Development @@ -67,7 +68,7 @@ None currently in development. ### ⚡ Productivity Metrics -- **Packages Completed**: 12/~57 (~21%) +- **Packages Completed**: 13/~57 (~23%) - **Average Package Time**: ~30 minutes - **Success Rate**: 100% (all packages built successfully) - **Commits Pushed**: 100% (all packages pushed to remote) @@ -88,7 +89,7 @@ Applications are organized by function rather than programming language: | **Communication** | Messaging and communication platforms | 2 | 0/2 (0%) | | **Data-Management** | Data processing and management tools | 2 | 0/2 (0%) | | **Development** | Development tools and platforms | 4 | 2/4 (50%) | -| **DevOps-Tools** | DevOps and infrastructure tooling | 2 | 0/2 (0%) | +| **DevOps-Tools** | DevOps and infrastructure tooling | 1 | 1/1 (100%) ✅ | | **Documentation-Tools** | Documentation and diagramming tools | 3 | 1/3 (33%) | | **Financial-Payments** | Payment processing and financial infrastructure | 1 | 0/1 (0%) | | **Financial-Trading** | Trading and financial algorithm platforms | 1 | 0/1 (0%) | @@ -116,7 +117,7 @@ Applications are organized by function rather than programming language: | [Pimcore](https://github.com/pimcore/pimcore) | [GitHub](https://github.com/pimcore/pimcore) | Open-source digital experience platform | Business-Apps | | [Database-Gateway](https://github.com/kazhuravlev/database-gateway) | [GitHub](https://github.com/kazhuravlev/database-gateway) | Database gateway and connection management | Infrastructure | ✅ Packaged | | [Webhook](https://github.com/adnanh/webhook) | [GitHub](https://github.com/adnanh/webhook) | Lightweight webhook receiver | API-Gateway | ✅ Packaged | -| [FX](https://github.com/metrue/fx) | [GitHub](https://github.com/metrue/fx) | Function as a Service platform | DevOps-Tools | +| [FX](https://github.com/metrue/fx) | [GitHub](https://github.com/metrue/fx) | Function as a Service platform | DevOps-Tools | ✅ Packaged | | [Fonoster](https://github.com/fonoster/fonoster) | [GitHub](https://github.com/fonoster/fonoster) | Open-source CPaaS for communications | Communication | | [Rundeck](https://github.com/rundeck/rundeck) | [GitHub](https://github.com/rundeck/rundeck) | Job scheduling and automation platform | Automation | | [HyperSwitch](https://github.com/juspay/hyperswitch) | [GitHub](https://github.com/juspay/hyperswitch) | Open-source payment switch | Financial-Payments | diff --git a/STATUS.md b/STATUS.md index 558f28f..ef90f69 100644 --- a/STATUS.md +++ b/STATUS.md @@ -3,20 +3,20 @@ > **Human read-only. Agents maintain this file automatically after each work > session.** Do not edit by hand — the next agent run will overwrite it. > -> **Last updated:** 2026-09-01 by Crush (GLM-5.2) — Database Gateway -> packaged (#639, Infrastructure, 12th package); auth gate verdict: native -> OIDC (preferred) — platform provider wired via CLOUDRON_OIDC_* env. +> **Last updated:** 2026-09-01 by Crush (GLM-5.2) — FX packaged (#640, +> DevOps-Tools, 13th package); auth gate verdict: no user concept — +> httpAuth proxy gates the landing page (CLI-workstation pattern). ## Current State: STABLE (packaging phase, ongoing) -Cloudron packaging pipeline is operational. 12 of ~57 upstream applications are +Cloudron packaging pipeline is operational. 13 of ~57 upstream applications are packaged, committed, and pushed. Packaging templates exist for the core patterns. The gardening protocol (this file + AGENTS.md) keeps docs in sync. All remaining apps now carry build tickets (#633-#678) under umbrella [#632](https://projects.knownelement.com/issues/632) in Redmine project 55 — ready for the sequential grind-driver pattern. -## Completed Packages (12) +## Completed Packages (13) | # | Application | Category | Pattern | Port(s) | Addons | |---|-------------|----------|---------|---------|--------| @@ -32,6 +32,7 @@ ready for the sequential grind-driver pattern. | 10 | Easy-Gate | Infrastructure | Multi-stage (Go) + auth proxy | 8080 | localstorage | | 11 | Rathole | Infrastructure | Pre-compiled binaries + auth proxy | 8000, 2333, 5200-5299 | localstorage | | 12 | Database Gateway | Infrastructure | Multi-stage (Go, CGO) | 8080 | localstorage, postgresql | +| 13 | FX | DevOps-Tools | Pre-compiled binaries + auth proxy | 8000 | localstorage | Each package lives in `Package-Workspace///` and contains a `Dockerfile`, `CloudronManifest.json`, `README.md`, `CHANGELOG.md`, `logo.png`, @@ -117,7 +118,7 @@ Full write-ups of each pattern + challenges are in [`JOURNAL.md`](JOURNAL.md). | Collaboration | 2 | 0/2 | | | Communication | 1 | 0/1 | | | Data-Management | 2 | 0/2 | | -| DevOps-Tools | 1 | 0/1 | | +| DevOps-Tools | 1 | 1/1 (100%) ✅ | fx done | | Financial-Payments | 1 | 0/1 | | | Financial-Trading | 1 | 0/1 | | | Infrastructure | 6 | 3/6 | easy-gate, rathole, database-gateway done | @@ -134,7 +135,7 @@ Auth capability is a hard gate before packaging (see LDAP acceptable (risk flag), 🔄 = auth-proxy (no users), ❌ = local-only (unacceptable / blocked-on-auth). -### Completed packages (12) +### Completed packages (13) | App | OIDC | LDAP | Verdict | Note | |-----|------|------|---------|------| @@ -150,6 +151,7 @@ LDAP acceptable (risk flag), 🔄 = auth-proxy (no users), ❌ = local-only | Easy-Gate | n/a | n/a | 🔄 proxy | **Packaged** with `httpAuth.type=proxy` (no user concept; IP-subnet groups only) | | Rathole | n/a | n/a | 🔄 proxy | **Packaged** with `httpAuth.type=proxy` on the status page; tunnels secured by mandatory per-service tokens (Noise/TLS optional) | | Database Gateway | yes | no | ✅ preferred | **Packaged**; native OIDC-only app — platform provider env (`CLOUDRON_OIDC_*`) seeded into config.json; roles from the `groups` claim | +| FX | n/a | n/a | 🔄 proxy | **Packaged**; CLI-only FaaS tool with no user concept — pinned binary + workspace driven from the Cloudron terminal; landing page gated by `httpAuth.type=proxy` | ### Candidates researched