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
This commit is contained in:
@@ -0,0 +1 @@
|
||||
repo/
|
||||
@@ -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: <user>@<host> 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
|
||||
@@ -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).
|
||||
@@ -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 <user>@<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"
|
||||
}
|
||||
@@ -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_<tag>_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"]
|
||||
@@ -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, `<user>@<host>`) 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)
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 5.8 KiB |
Executable
+40
@@ -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 <user>@<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
|
||||
@@ -0,0 +1,58 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>FX</title>
|
||||
<style>
|
||||
body { font-family: -apple-system, "Segoe UI", Roboto, sans-serif; background: #14171c; color: #d8dee6; margin: 0; display: flex; min-height: 100vh; align-items: center; justify-content: center; }
|
||||
main { max-width: 640px; padding: 2rem; }
|
||||
h1 { color: #ffffff; font-size: 1.6rem; margin-bottom: 0.25rem; }
|
||||
p.tagline { color: #7f8c9b; margin-top: 0; }
|
||||
code { background: #1f242c; border: 1px solid #2d3440; border-radius: 4px; padding: 0.1rem 0.35rem; font-size: 0.85rem; color: #9ecbff; }
|
||||
table { border-collapse: collapse; width: 100%; margin: 1rem 0; font-size: 0.9rem; }
|
||||
th, td { text-align: left; padding: 0.5rem 0.75rem; border-bottom: 1px solid #2d3440; }
|
||||
th { color: #7f8c9b; font-weight: 600; }
|
||||
pre { background: #1f242c; border: 1px solid #2d3440; border-radius: 6px; padding: 1rem; overflow-x: auto; font-size: 0.8rem; line-height: 1.5; }
|
||||
.ok { color: #6bc46d; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>FX <span class="ok">▲ fx 0.9.48 ready</span></h1>
|
||||
<p class="tagline">Poor man's function as a service</p>
|
||||
|
||||
<p>fx is a <strong>CLI, not a web app</strong> — 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 <strong>web
|
||||
terminal</strong>: open the terminal and run <code>fx -v</code> 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 <em>target</em> you point fx at, never inside this
|
||||
app.</p>
|
||||
|
||||
<table>
|
||||
<tr><th>Path</th><th>Purpose</th></tr>
|
||||
<tr><td><code>/app/data/functions/</code></td><td>Function sources (<code>hello.js</code> + <code>hello.py</code> starters seeded)</td></tr>
|
||||
<tr><td><code>/app/data/ssh/</code></td><td>SSH keys for remote Docker targets (fx logs in with a key, no passwords)</td></tr>
|
||||
<tr><td><code>/app/data/kube/</code></td><td>kubeconfig for Kubernetes targets</td></tr>
|
||||
</table>
|
||||
|
||||
<p>Deploy to a remote Docker host (JS example):</p>
|
||||
<pre>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</pre>
|
||||
|
||||
<p>Deploy to Kubernetes (copy your kubeconfig to
|
||||
<code>/app/data/kube/config</code> first):</p>
|
||||
<pre>export FX_KUBECONF=/app/data/kube/config
|
||||
fx up --name hello /app/data/functions/hello.py</pre>
|
||||
|
||||
<p style="color:#7f8c9b;font-size:0.85rem;">Supported languages: Go,
|
||||
JavaScript, Python, Ruby, Java, PHP, Perl, Crystal, Rust, Julia, D.
|
||||
Upstream docs:
|
||||
<a style="color:#9ecbff" href="https://github.com/metrue/fx">github.com/metrue/fx</a></p>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user