Package Penpot 2.18.3 for Nix with NixOS module; afk devShell
Vendored from debling/nix-penpot (MIT). Fixes on top: - pnpm fetchers: drop --config.fetch-* flags (UND_ERR_INVALID_ARG on pnpm 11.27) and pin real store hashes (frontend x3, exporter, mcp) - deps-lock.json: add transitive funcool/beicon2 v2.0 so fake-git resolves potok2's pin during the shadow-cljs classpath build - expose frontend pnpm stores via passthru for rehashing - x86_64-linux only; devShell ships afk, jj, git
This commit is contained in:
commit
08b5496980
21 files changed
+7840
No files matched your search
@@ -0,0 +1,156 @@
|
||||
{
|
||||
lib,
|
||||
self,
|
||||
...
|
||||
}:
|
||||
{
|
||||
name = "penpot";
|
||||
meta.maintainers = with lib.maintainers; [ ];
|
||||
|
||||
nodes.machine =
|
||||
{ lib, ... }:
|
||||
{
|
||||
imports = [
|
||||
{ nixpkgs.overlays = [ self.overlays.default ]; }
|
||||
self.nixosModules.default
|
||||
./penpot-config.nix
|
||||
];
|
||||
|
||||
# Room for the backend JVM, the exporter's Chromium pool and
|
||||
# PostgreSQL during boot.
|
||||
virtualisation.memorySize = 3072;
|
||||
virtualisation.diskSize = 4096;
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
machine.wait_for_unit("postgresql.service")
|
||||
machine.wait_for_unit("redis-penpot.service")
|
||||
machine.wait_for_unit("mailpit-penpot.service")
|
||||
machine.wait_for_unit("nginx.service")
|
||||
machine.wait_for_unit("penpot-backend.service")
|
||||
|
||||
def http(url, extra="", fail=True):
|
||||
"""Fetch url with curl and return the response body or headers.
|
||||
|
||||
fail=False keeps going on HTTP error statuses, for endpoints that
|
||||
answer with a useful body on them (e.g. the MCP 406)."""
|
||||
flag = "-f " if fail else ""
|
||||
return machine.succeed("curl -sS " + flag + extra + " " + url)
|
||||
|
||||
def http_code(url, extra=""):
|
||||
return machine.succeed(
|
||||
f"curl -sS -o /dev/null -w '%{{http_code}}' {extra} {url}"
|
||||
).strip()
|
||||
|
||||
mcp_init = (
|
||||
"-X POST -H 'Content-Type: application/json' "
|
||||
"-d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{}}'"
|
||||
)
|
||||
|
||||
# The backend is a source-run Clojure uberjar: give it a generous window
|
||||
# to reach the readyz endpoint (which also exercises the database).
|
||||
machine.wait_until_succeeds(
|
||||
"curl -fsS http://localhost:8080/readyz -o /tmp/readyz && grep -q OK /tmp/readyz",
|
||||
timeout=900,
|
||||
)
|
||||
|
||||
# Provisioning and secret handling.
|
||||
machine.succeed("systemctl is-active penpot-db-setup.service")
|
||||
dbowner = machine.succeed(
|
||||
"su postgres -c \"psql -tAc \\\"SELECT pg_get_userbyid(datdba)"
|
||||
" FROM pg_database WHERE datname='penpot'\\\"\""
|
||||
)
|
||||
assert "penpot" in dbowner
|
||||
machine.succeed("test -d /var/lib/penpot/assets")
|
||||
|
||||
# Frontend static site and rendered configuration.
|
||||
assert "<!doctype html" in http("http://localhost:8080/").lower()
|
||||
config_js = http("http://localhost:8080/js/config.js")
|
||||
for needle in ("penpotPublicURI", "disable-secure-session-cookies", "enable-smtp"):
|
||||
assert needle in config_js
|
||||
assert "no-store" in http("http://localhost:8080/js/config.js", "-I").lower()
|
||||
assert len(http("http://localhost:8080/js/worker/main.js")) > 0
|
||||
assert "max-age=604800" in http("http://localhost:8080/js/worker/main.js", "-I")
|
||||
|
||||
# Exporter and MCP entry points.
|
||||
machine.wait_for_unit("penpot-exporter.service")
|
||||
machine.wait_until_succeeds(
|
||||
"curl -fsS http://127.0.0.1:6061/readyz -o /tmp/exporter-readyz"
|
||||
" && grep -q OK /tmp/exporter-readyz",
|
||||
timeout=120,
|
||||
)
|
||||
machine.wait_for_unit("penpot-mcp.service")
|
||||
machine.wait_for_open_port(4401)
|
||||
# In multi-user mode a tokenless initialize is answered with a JSON-RPC
|
||||
# error (HTTP 406); any well-formed JSON-RPC response proves the server
|
||||
# is serving the MCP protocol.
|
||||
assert "jsonrpc" in http("http://127.0.0.1:4401/mcp", mcp_init, fail=False)
|
||||
|
||||
# The vhost routes /api/export to the exporter: the status code via the
|
||||
# vhost equals the one from hitting the exporter directly (the exporter
|
||||
# answers unauthenticated GETs with its own validation error), proving
|
||||
# the proxy hop without hardcoding a status.
|
||||
assert http_code("http://localhost:8080/api/export/readyz") == http_code(
|
||||
"http://127.0.0.1:6061/api/export/readyz"
|
||||
)
|
||||
assert "jsonrpc" in http("http://localhost:8080/mcp/stream", mcp_init, fail=False)
|
||||
|
||||
# Administrative CLI bootstrap path (no email roundtrip): the
|
||||
# penpot-manage tool ships on the machine PATH and talks to the backend
|
||||
# prepl server; profiles created through it are active immediately.
|
||||
machine.succeed(
|
||||
"penpot-manage create-profile -n 'CLI Admin'"
|
||||
" -e cli@penpot.local -p cli-password-123"
|
||||
)
|
||||
cli_login = http(
|
||||
"http://localhost:8080/api/rpc/command/login-with-password",
|
||||
"-X POST -H 'Content-Type: application/transit+json' "
|
||||
+ "-d '[\"^ \",\"~:email\",\"cli@penpot.local\","
|
||||
+ "\"~:password\",\"cli-password-123\"]'",
|
||||
fail=False,
|
||||
)
|
||||
assert "cli@penpot.local" in cli_login
|
||||
assert '"~:is-admin",false' in cli_login
|
||||
|
||||
# Real API roundtrip (nginx -> backend -> database): an unknown user
|
||||
# login gets the backend's own validation error back.
|
||||
assert "wrong-credentials" in http(
|
||||
"http://localhost:8080/api/rpc/command/login-with-password",
|
||||
"-X POST -H 'Content-Type: application/transit+json' "
|
||||
+ "-d '[\"^ \",\"~:email\",\"probe@example.org\","
|
||||
+ "\"~:password\",\"wrong-password-123\"]'",
|
||||
fail=False,
|
||||
)
|
||||
|
||||
# Websocket proxying path answers (backend not fully upgraded without a
|
||||
# client, but the vhost must not 404 on it).
|
||||
assert http_code(
|
||||
"http://localhost:8080/ws/notifications",
|
||||
"-H 'Upgrade: websocket' -H 'Connection: Upgrade'",
|
||||
) in ("426", "400", "101")
|
||||
|
||||
# SMTP sink: deliver a mail through mailpit via curl's SMTP client and
|
||||
# read it back through the mailpit API.
|
||||
machine.succeed(
|
||||
"printf 'Subject: penpot-test\\r\\nFrom: test@example.org\\r\\nTo:"
|
||||
" test@example.org\\r\\n\\r\\nhello from the test\\r\\n'"
|
||||
" | curl -sS smtp://127.0.0.1:1025"
|
||||
" --mail-from test@example.org --mail-rcpt test@example.org -T -"
|
||||
)
|
||||
machine.wait_until_succeeds(
|
||||
"curl -fsS 'http://127.0.0.1:8025/api/v1/messages' -o /tmp/mailpit"
|
||||
" && grep -q penpot-test /tmp/mailpit",
|
||||
timeout=60,
|
||||
)
|
||||
|
||||
# Restart semantics: the backend must come back to a healthy state
|
||||
# after a restart.
|
||||
machine.succeed("systemctl restart penpot-backend.service")
|
||||
machine.wait_until_succeeds(
|
||||
"curl -fsS http://localhost:8080/readyz -o /tmp/readyz2 && grep -q OK /tmp/readyz2",
|
||||
timeout=900,
|
||||
)
|
||||
'';
|
||||
}
|
||||
Reference in new issue
Block a user