{"id":20141257,"url":"https://github.com/alekcz/pcp","last_synced_at":"2025-06-25T05:44:49.810Z","repository":{"id":43222013,"uuid":"248926887","full_name":"alekcz/pcp","owner":"alekcz","description":"PCP: Clojure Processor -- A Clojure replacement for PHP","archived":false,"fork":false,"pushed_at":"2022-03-12T11:07:05.000Z","size":4956,"stargazers_count":233,"open_issues_count":9,"forks_count":5,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-06-11T08:28:07.155Z","etag":null,"topics":["clojure","nginx","pcp"],"latest_commit_sha":null,"homepage":"https://clojure-pulse.musketeers.io","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alekcz.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-03-21T07:22:50.000Z","updated_at":"2025-04-07T13:45:20.000Z","dependencies_parsed_at":"2022-08-28T03:14:20.098Z","dependency_job_id":null,"html_url":"https://github.com/alekcz/pcp","commit_stats":null,"previous_names":[],"tags_count":37,"template":false,"template_full_name":null,"purl":"pkg:github/alekcz/pcp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alekcz%2Fpcp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alekcz%2Fpcp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alekcz%2Fpcp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alekcz%2Fpcp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alekcz","download_url":"https://codeload.github.com/alekcz/pcp/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alekcz%2Fpcp/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260886314,"owners_count":23077178,"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":["clojure","nginx","pcp"],"created_at":"2024-11-13T21:56:51.928Z","updated_at":"2025-06-25T05:44:49.760Z","avatar_url":"https://github.com/alekcz.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cimg src=\"assets/logo/logo-alt.svg\" width=\"200px\"\u003e\n\n![master](https://github.com/alekcz/pcp/workflows/master/badge.svg) ![codecov](https://codecov.io/gh/alekcz/pcp/branch/master/graph/badge.svg) ![downloads](https://img.shields.io/github/downloads/alekcz/pcp/total)  \n\n**Welcome to PCP**\n\n\u003e PCP: Clojure Processor -- _Like drugs but better_  \n\n## Introduction\n\nToo long have we hustled to deploy Clojure websites. Too long have we spun up one server instance per site. Too long have we reminisced about the simplicty of deploying PHP. Today we enjoy the benefits of both. Welcome to PCP.\n\n### Status\nActive development. Stabilizing.     \nLatest version: `v0.0.2`\n\n### Goals\n\n* An easy to use, drop-in Clojure replacement for php scripts\n* Allow multiple websites to be hosted on a single $5 VPS\n\n### Non-goals\n\n* Performance.  _PCP should be sufficient for prototypes and small websites  (\u003c 1400 req/s)_\n\n### Demo site\n\nYou can view a demo site built in PCP here: [https://clojure-pulse.musketeers.io](https://clojure-pulse.musketeers.io/)\n\n## Overview\n\n1. [Quick start](#quick-start)\n1. [How PCP works](#how-pcp-works)\n1. [System requirements](#system-requirements)\n1. [Project structure and requiring files](#project-structure-and-requiring-files)\n1. [Environment variables and secrets](#environment-variables-and-secrets)\n1. [Core PCP namespace](#core-pcp-namespace)\n1. [Other built into PCP](#other-built-in-namespaces)\n1. [Performance](#performance)\n1. [Deploying PCP](#deploying-pcp)\n1. [Roadmap](#performance)\n\n## Quick start\nInstall pcp via the installer script:\n``` shellsession\n$ bash -c \"$(curl -sSL https://raw.githubusercontent.com/alekcz/pcp/master/install.sh)\"\n```\n\nQuery the PCP service status:\n``` shellsession\n$ pcp service status\n```\n\nCreate a new project:\n``` shellsession\n$ pcp new project-name\n```\n\nWhen you create a new project some example code is generated as well which, for convenience, you can view using the local server built in to the pcp utility. This local server behaves as your pcp site would when deployed with nginx.\n\n``` shellsession\n$ cd project-name\n$ pcp -s public/\n```\n\nNavigate to [http://localhost:3000/](http://localhost:3000/) and voilà.\n\n\u003cimg src=\"assets/screenshots/preview.png\" width=\"800px\"\u003e\n\nThis is the corresponding code. \n\n```clojure\n(ns index\n  (:require [pcp :as pcp] ;this is the core namespace with various useful functions\n            [api.info :as a] ;this is another name space in the same project. \n            [garden.core :refer [css]])) ;this is 3rd party namespace from the hosted environment\n\n(def resp \n  (pcp/html\n    [:html {:style \"font-family: 'Source Sans Pro', Arial, Helvetica, sans-serif;\"\n            :lang \"en\"}\n      [:head \n        [:title \"PCP website\"]\n        [:style \n          (css [:html   { :text-align \"center\"}])\n          (css [:p.info { :background-color \"#EEE\" \n                          :font-size \"13px\"\n                          :margin-top \"40px\"\n                          :padding \"10px\" \n                          :text-align \"left\" \n                          :width \"300px\"}])\n          (css [:strong { :font-size \"13px\"}])\n          (css [:code   { :font-size \"12px\" :font-weight \"normal\"}])\n          (css [:.main  { :display \"flex\"\n                          :flex-direction \"column\"\n                          :justify-content \"center\"\n                          :align-items \"center\"\n                          :min-height \"90vh\" \n                          :font-weight \"normal\"}])]]\n      [:body \n        [:div.main \n          [:img \n            {:src \"//raw.githubusercontent.com/alekcz/pcp/master/assets/logo/logo-alt.svg\" \n             :width \"200px\"}]\n          [:h1 \"Your PCP site is up and running.\"]\n          [:p \"Your json endpoint is here \" [:a {:href \"/api/info.clj\"} \"here\"] \n            [:br]\n            \"Learn more about pcp at \" [:a {:href a/repo :target \"_blank\"} \"here\"]\n            [:br]\n            \"Happy scripting!\"]\n          [:p.info\n            [:strong \"Request uri \"] [:span (str (-\u003e pcp/request :request-uri))]\n            [:br] \n            [:strong \"Request method \"] [:span (str (-\u003e pcp/request :request-method))]\n            [:br] \n            [:strong \"Request scheme \"] [:span (str (-\u003e pcp/request :request-scheme))]\n            [:br] \n            [:strong \"User agent \"]\n            [:br] \n            [:code (str (-\u003e pcp/request :http-user-agent))]\n            [:br]]]]]))\n\n(pcp/response 200 resp \"text/html\")            \n\n```\n\n\nWhen navigating to your site, if you see `Connection refused (Connection refused)` it means the PCP service not running. It could still be booting or be down. \n\nYou can find instructions on [replacing php and deploying to production here](./docs/replacing-php.md)\n\n## How PCP works\nPCP has two parts. The utility is a simple binary, built with GraalVM, that allows you to work effectively with pcp. \n\n```\nPCP: Clojure Processor -- Like drugs but better\n\nUsage: pcp [option] [value]\n\nOptions:\n  new [project]           Create a new pcp project in the [project] directory\n  service [stop/start]    Stop/start the PCP service\n  passphrase [project]    Set passphrase for [project]\n  secret [path]           Add and encrypt secrets at . or [path]\n  secret [path]           Add and encrypt secrets at . or [path]\n  -e, --evaluate [path]   Evaluate a clojure file using PCP\n  -s, --serve [root]      Start a local server at . or [root]\n  -v, --version           Print the version string and exit\n  -h, --help              Print the command line help\n```      \nThe heavy lifting is done by an PCP service. This server runs on port 9000 and receives requests from the local pcp server or from nginx. The PCP service is a uberjar that runs as a daemon.\n\n## System requirements\nPCP is designed to work on Linux and OSX. It requires the following to work:  \n\n- bash\n- java 8+\n- systemd / launchd\n- unzip\n- persitent disk space\n- nginx (for production deployment)\n\n\n## Project structure and requiring files\n\nRequiring files in PCP mostly works as it does in Clojure, the primary exception being that a project cannot require a file outside of its root. \nGiven a project like this:\n\n```\n.                           # project root    \n├── .pcp      \n│   └── a24d...cfdcb8.npy  # encrypted secret, touch this and bad things will happen    \n├── public                 # server root    \n│   ├── index.clj          \n│   └── login.clj    \n│   └── dashboard.clj    \n│   └── logic    \n│       └── orders.clj    \n│       └── payments.clj    \n│       └── stock.clj    \n│       └── users.clj    \n├── pcp.edn                # pcp configuration file    \n└── README.md              # surely you don't need a comment to tell you what this is.    \n```    \n   \nYour dashboard could require business logic as follows\n```clojure\n(ns dashboard\n  (:require [logic.users :as lu]\n            [logic.orders :as lo]\n            [logic.stock :as ls]\n            [logic.payments :as lp]))\n```\n\n## Environment variables and secrets\n\nTo allow API keys, tokens, and the like to be stored and retrieved securely, PCP encrypts them and stores them in the project. Secrets are created using the PCP CLI. They are encrypted using the passphrase selected. Here is an example of the process. \n\n```shellsession\n$ pcp secret\n--------------------------------------------------\nSet an encrypted secret variable for project: pcp-demo\nPlease ensure you use the same passphrase for all your secrets in this project\nand that you add your passphrase to your production server using:\n  pcp passphrase pcp-demo\n--------------------------------------------------\nSecret name: GITHUB_PERSONAL_ACCESS_TOKEN\nSecret value: ghp_eR17e5vHq0Sdmj22oracJd0Y7je1IM3g7oPV7yT7sq31\nPassphrase: my-super-secure-passphrase-that-i-will-also-store-on-the-server    \nencrypting...\ndone.\n```\n\nSymmetric encryption is used, so the passphrase needs to be added to the server too.    \n```shellsession\n$ pcp passphrase pcp-demo\n--------------------------------------------------\nSet passphrase for project: pcp-demo\nThis passphrase will be used for decrypting secrets at runtime.\n--------------------------------------------------\nPassphrase: my-super-secure-passphrase-that-i-will-also-store-on-the-server    \n```\n\n## PCP namespace\n\nThe following functions are part of the core PCP namespace and are made available for convenience. \n\n#### pcp/persist\n`(pcp/persist :cache-key  f-on-miss)`    \nThis macro allows expensive operations to only be recomputed if they are not in the cache.   \nOn a cache miss, the `f-on-miss` is computed, its value stored in the cache and returned. Caches are isolated\nacross projects. Pages in the same project share cache keys. The cache uses a TTL (30 min) as its invalidation strategy. \n\n#### pcp/clear\n`(pcp/clear :cache-key)`    \nRemoves a key from the cache\n\n#### pcp/request\n`pcp/request`    \nThe request map. The request map conforms to the [ring spec](https://github.com/ring-clojure/ring/blob/master/SPEC). \n\n#### pcp/response\n`(pcp/response status body mime-type)`        \nA convenience function for generating a response map. Response map conforms to the [ring spec](https://github.com/ring-clojure/ring/blob/master/SPEC).\n\n#### pcp/redirect\n`(pcp/redirect target)`        \nA convenience function for generating a redirect. Response map conforms to the [ring spec](https://github.com/ring-clojure/ring/blob/master/SPEC).\n\n#### pcp/render-html\n`(pcp/render-html options \u0026 content)`    \nRenders html from Clojure data structures using [hiccup](https://github.com/weavejester/hiccup)\n\n#### pcp/secret\n`(pcp/secret \"SECRET_NAME\")`    \nRetrieves a secret from the project. The secret is read from disk. It may be worthwhile using `pcp/persist` to improve performance. \nSee [Environment variables and secrets](#environment-variables-and-secrets) for more info.\n\n#### pcp/now\n`(pcp/now)`    \nReturns the current time in milliseconds (according to your server).\n\n#### pcp/slurp\n`(pcp/slurp \"../file-name\")`    \nOpens a reader on f and reads all its contents, returning a string. Cannot access files higher than project root (i.e. server root -1). Does not currently accept any optional arguments.\n\n#### pcp/slurp-upload\n`(pcp/slurp-upload \"/var/tmp/temp-file-name\")`    \n`(pcp/slurp-upload (-\u003e pcp/request :params :uploaded :tempfile))`    \nOpens a reader on f and reads all its contents, returning a string. Cannot access files higher up that project root (i.e. server root -1). Does not currently accept any optional arguments.\n\n\n#### pcp/spit\n`(pcp/spit \"../file-name\" \"like drugs but better\")`    \nOpposite of slurp. Opens f with writer, writes content, then closes f. Cannot access files higher than project root (i.e. server root -1). Does not currently accept any optional arguments.\n\n\n## Other built-in namespaces\n\nIn addition to the core clojure namespaces in [sci](https://github.com/borkdude/sci), the following namespaces are also available.\n\n  - `clojure.string`\n  - `clojure.core.async` \n  - `clojure.edn`\n  - `cheshire.core`\n  - `selmer.parser`\n  - `selmer.filters`\n  - `org.httpkit.client`\n  - `org.httpkit.sni-client`\n  - `next.jdbc`\n  - `honeysql.core`\n  - `honeysql.helpers`\n  - `postal.core`\n  - `tick.alpha.api`\n  - `buddy.sign.jwt` \n  - `buddy.sign.jwe`\n  - `buddy.core.hash`\n  - `buddy.core.codecs`\n  - `buddy.core.keys` \n  - `buddy.auth.backends`\n  - `buddy.auth.middleware`\n  - `buddy.hashers` \n  - `garden.core` \n  - `garden.stylesheet`\n  - `garden.units` \n  - `konserve.core`\n  - `konserve.filestore`\n  - `konserve-jdbc.core` \n    \n\n## Performance\nA modest PCP website can handle its own pretty well.     \nThis test was run for 90 minutes with 500 VUs and 1400 req/s. Of the 7.7M requests generated and 0 failed. \n\n\u003cimg src=\"assets/performance/k6-0.0.2.png\" width=\"800px\"\u003e\n\nYou can see the spike in CPU and bandwidth on the $5 droplet, but the CPU never saturates. \n\n\u003cimg src=\"assets/performance/do-0.0.2.png\" width=\"800px\"\u003e\n\nThese are by no means comprehensive benchmarks but give you a sense of what a PCP server can withstand for a simple use case.\nGoing above 1400 req/s generally results in bad things happening. You can test your own site using [k6](https://k6.io/) with the instructions in [loadtest.js](./loadtest.js)\n\n## Deploying PCP\n\nYou can find instructions on deploying your PCP site with nginx [here](./docs/replacing-php.md).   \n  \nOr you could just use this nifty button to deploy to DigitalOcean (experimental).  \n\n[![Deploy to DO](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/apps/new?repo=https://github.com/alekcz/pcp-template/tree/master\u0026refcode=a0cfd79e40a2)\n\nWe both get credits for DigitalOcean if you end using their services so be a mate. \n\n\n# Roadmap \u0026 releases\n\n## Release 0.0.1\n- [x] Run arbitrary scripts\n- [x] Generate HTML with hiccup\n- [x] Invoke scripts from Nginx\n- [x] Emulate Nginx environment locally\n- [x] Store secrets in projects\n- [x] Restrict `pcp/slurp` and `pcp/spit` to project root\n- [x] Cache expensive operations\n- [x] Expose request data to scripts\n- [x] Require external namespaces as in Clojure\n- [x] Generate new project from CLI\n- [x] Document PCP\n- [x] Load test a production deployment\n\n## Release 0.0.2\n- [x] Perfomance improvements.\n- [x] Only read source file from disk if it is not in cache (LRU) or has been modified\n- [x] Add `pcp/slurp-upload` for reading temporary files\n- [x] Add `pcp/redirect` for convenience redirect\n\n## Release 0.1.0\n- [ ] Store passphrases with [konserve](https://github.com/replikativ/konserve)\n- [ ] Add sponsorship button\n- [ ] Deploy without ssh-ing into VPS\n- [ ] More robust CLI processing\n- [ ] Automate deployment from repo\n\n## Release 0.2.0\n- [ ] Add PEDL (PCP, Nginx, Datahike Server, and Linux) stack template on Digital Ocean. \n- [ ] Other things that even the wisest cannont tell; things that were, things that are, and some things... that have not yet come to pass. \n\n\n## Special thanks\nFor the guidance and examples, special thanks to\n\n- [@BrunoBonacci](https://github.com/BrunoBonacci) \n- [@borkdude](https://github.com/borkdude) \n- [@Baeldung](https://twitter.com/Baeldung)\n\n## License\n\nCopyright © 2020 Alexander Oloo\n\nPCP is licensed under the MIT License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falekcz%2Fpcp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falekcz%2Fpcp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falekcz%2Fpcp/lists"}