{"id":16964957,"url":"https://github.com/propensive/ethereal","last_synced_at":"2025-10-25T23:51:37.812Z","repository":{"id":207518300,"uuid":"719444985","full_name":"propensive/ethereal","owner":"propensive","description":"Run Scala applications as daemons","archived":false,"fork":false,"pushed_at":"2025-02-11T21:13:09.000Z","size":3779,"stargazers_count":4,"open_issues_count":2,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-10T19:53:46.814Z","etag":null,"topics":["cli-app","client-server-application","daemon","nailgun"],"latest_commit_sha":null,"homepage":"https://soundness.dev/ethereal/","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/propensive.png","metadata":{"files":{"readme":".github/readme.md","changelog":null,"contributing":".github/contributing.md","funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-11-16T07:18:39.000Z","updated_at":"2025-02-11T21:13:12.000Z","dependencies_parsed_at":"2024-01-05T10:52:40.159Z","dependency_job_id":"ed59213e-7033-4672-b405-c34b18a10b7e","html_url":"https://github.com/propensive/ethereal","commit_stats":null,"previous_names":["propensive/spectral"],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fethereal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fethereal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fethereal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fethereal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/propensive","download_url":"https://codeload.github.com/propensive/ethereal/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244217806,"owners_count":20417665,"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":["cli-app","client-server-application","daemon","nailgun"],"created_at":"2024-10-13T23:44:41.400Z","updated_at":"2025-10-25T23:51:32.777Z","avatar_url":"https://github.com/propensive.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"[\u003cimg alt=\"GitHub Workflow\" src=\"https://img.shields.io/github/actions/workflow/status/propensive/ethereal/main.yml?style=for-the-badge\" height=\"24\"\u003e](https://github.com/propensive/ethereal/actions)\n[\u003cimg src=\"https://img.shields.io/discord/633198088311537684?color=8899f7\u0026label=DISCORD\u0026style=for-the-badge\" height=\"24\"\u003e](https://discord.com/invite/MBUrkTgMnA)\n\u003cimg src=\"/doc/images/github.png\" valign=\"middle\"\u003e\n\n# Ethereal\n\n__Launch Scala applications lightning-fast by running them as daemons__\n\n_Ethereal_ provides the capability to turn an existing Scala command-line application into a daemon for fast startup\n(like [Nailgun](https://github.com/facebookarchive/nailgun)). This is particularly useful when combined with\n[Exoskeleton](https://github.com/propensive/exoskeleton) for tab completions.\n\nEthereal's launcher script is written in around 100 lines of zero-dependency Bash, and can be used to transform a\nScala application into a self-contained, portable executable.\n\n## Features\n\n- fast daemon process for near-instantaneous invocations\n- safe concurrent invocations so that at most one daemon is running at any time\n- portable launcher written in Bash\n- facilitates single-file distributions of Scala executables\n- allows easy bundling with a JDK or JRE for distribution as a single file\n- support for messaging between clients\n\n\n\n## Availability\n\n\n\n\n\n\n\n## Getting Started\n\n### Running as a Daemon\n\nJava is known for its slow startup time compared to other languages. While that has improved significantly in\nmore recent versions, waiting a fraction of a second is typical, even for a trivial \"Hello world\" application. This is\nunacceptable for many purposes, in particular, providing dynamic tab-completions.\n\nFurthermore, many programs run faster, the longer they have been running, thanks to the HotSpot just-in-time\ncompiler. This advantage returns to zero every time the application is started.\n\nAny runtime state must also be reconstructed every time the application launches.\n\nEthereal makes it easy to write a program which runs as a daemon. The first time it is called, it starts the\nJVM as a background process which listens for socket connections on a free local port, and all subsequent\ninvocations connect to the running JVM through sockets.\n\nA \"launcher script\" written in Bash handles this and a few other tasks:\n1. connecting to a port if the daemon is already running\n2. launching the daemon if it is not running\n3. forwarding environment information (environment variables, working directory, etc.) to the daemon\n4. switching to an appropriate TTY mode\n5. handling standard input and output\n6. forwarding interrupts received by the script to the daemon\n7. returning the exit status when the request has been handled\n\nNailgun provides two launcher clients written in C and Python. Unfortunately, the former must be installed\nseparately, distributed as multiple binaries for different CPU architectures, or compiled with a C compiler\nbefore use. And the latter has noticeably poor performance for each invocation.\n\nEthereal's launcher, which is written in Bash, is both portable and fast.\n\nThe daemon implementation is an ordinary method call which primarily specifies the code to be invoked for each\ninvocation, and would typically be the implementation of a main method,\n```scala\n@main\ndef myapp(): Unit = cliService:\n  Out.println(t\"Hello world\")\n  ExitStatus.Ok\n```\nor:\n```scala\nobject MyApp:\n  def main(args: IArray[String]): Unit = cliService:\n    Out.println(t\"Hello world\")\n    ExitStatus.Ok\n```\n\nWhen invoking this `main` method, the code inside the `cliService` block is not run immediately, but a server is\nstarted which listens on a free port, and each time it receives a request on that port, _then_ will the code in\nthe block be executed. The context of a `cliService` block provides all the same context as an `application` block,\nso it's very easy to convert a non-daemon application into a daemon.\n\nNote that unlike an `application` block, no arguments need to be passed to the the `cliService` block, but the\n`arguments` and `parameters` methods can be used to get the arguments or parameters for the current invocation.\n\nThe `Out` object from [Turbulence](https://github.com/propensive/turbulence/) (or `Err`) must be used for\nproducing output, since the default `println` method defined in Scala's `Predef` prints to the JVM's global\n`System.out`, which corresponds to standard output for the _daemon_ process; not any of the clients. Unless\n`System.out` is configured to do something else, its output will be lost.\n\n### Packaging\n\nThe launcher script is designed to be invoke a self-contained \"fat JAR\" file with a `Main-Class` declaration in\nits manifest. But furthermore, it can be bundled with the JAR as a single executable file.\n\nThis is possible thanks to a feature of JAR files (which are just ZIP files with a different extension): the\nindex of a ZIP file is at the end, and refers to data within the file as a (negative) offset from the end. This\nmeans that a ZIP file can be prefixed with unrelated data, without affecting its contents or ability to be read.\n\nSo a new file consisting of the launcher script followed by the JAR file can be distributed as a single,\nstandalone, portable, executable file. The launcher script simply calls `java -jar` passing _its own path_ as\nthe parameter, since it is simultaneously both a script and a JAR file.\n\nThe executable can be packaged using,\n```bash\ncat launcher application.jar \u003e application\nchmod +x application\n```\n\n### Messaging between daemon clients\n\nAn application running as a daemon can, of course, have multiple clients connected simultaneously. Normally,\nthese would be independent, with no interaction between them.\n\nHowever, there are times when it's useful for one client to communicate with the others, and a _bus_ is provided\nto facilitate this. A bus allows one client to pass messages of some type to _all_ the other clients. It's not\npossible to send messages just to one particular client, although any client could decide whether a particular\nmessage is relevant to it.\n\nTo use the bus facility, the daemon should have a type parameter specified when it is invoked, for example:\n```scala\n@main\ndef myapp(): Unit = cliService[Text]:\n  // body\n  ExitStatus.Ok\n```\n\nThis may be any `Matchable` type, and determines what values may be broadcast to other clients, and conversely,\nwhat values may be received (and therefore should be handled).\n\nA message may be sent to all other clients by calling `broadcast` from within a `daemon` block, for example,\n```scala\nenum BusMessage:\n  case Hello, Goodbye\n\ndef myapp(): Unit = cliService[BusMessage]:\n  broadcast(BusMessage.Hello)\n  // do something\n  broadcast(BusMessage.Goodbye)\n  ExitStatus.Ok\n```\n\nIf such messages are sent, then they ought to be received too! The contextual method, `bus`, provides a stream\nof messages which can be processed while the client is running. However, while the clients may be doing\ndifferent things at different times, they are homogeneous, so in order to be useful, the same client\nimplementation should be both sending and receiving messages on the bus.\n\nA typical implementation might multiplex the event stream from a\n[Profanity](https://github.com/propensive/profanity/) terminal with the bus, and handle keypresses and messages\nfrom other clients in an event loop.\n\n\n## Status\n\nEthereal is classified as __maturescent__. For reference, Soundness projects are\ncategorized into one of the following five stability levels:\n\n- _embryonic_: for experimental or demonstrative purposes only, without any guarantees of longevity\n- _fledgling_: of proven utility, seeking contributions, but liable to significant redesigns\n- _maturescent_: major design decisions broady settled, seeking probatory adoption and refinement\n- _dependable_: production-ready, subject to controlled ongoing maintenance and enhancement; tagged as version `1.0.0` or later\n- _adamantine_: proven, reliable and production-ready, with no further breaking changes ever anticipated\n\nProjects at any stability level, even _embryonic_ projects, can still be used,\nas long as caution is taken to avoid a mismatch between the project's stability\nlevel and the required stability and maintainability of your own project.\n\nEthereal is designed to be _small_. Its entire source code currently consists\nof 475 lines of code.\n\n## Building\n\nEthereal will ultimately be built by Fury, when it is published. In the\nmeantime, two possibilities are offered, however they are acknowledged to be\nfragile, inadequately tested, and unsuitable for anything more than\nexperimentation. They are provided only for the necessity of providing _some_\nanswer to the question, \"how can I try Ethereal?\".\n\n1. *Copy the sources into your own project*\n   \n   Read the `fury` file in the repository root to understand Ethereal's build\n   structure, dependencies and source location; the file format should be short\n   and quite intuitive. Copy the sources into a source directory in your own\n   project, then repeat (recursively) for each of the dependencies.\n\n   The sources are compiled against the latest nightly release of Scala 3.\n   There should be no problem to compile the project together with all of its\n   dependencies in a single compilation.\n\n2. *Build with [Wrath](https://github.com/propensive/wrath/)*\n\n   Wrath is a bootstrapping script for building Ethereal and other projects in\n   the absence of a fully-featured build tool. It is designed to read the `fury`\n   file in the project directory, and produce a collection of JAR files which can\n   be added to a classpath, by compiling the project and all of its dependencies,\n   including the Scala compiler itself.\n   \n   Download the latest version of\n   [`wrath`](https://github.com/propensive/wrath/releases/latest), make it\n   executable, and add it to your path, for example by copying it to\n   `/usr/local/bin/`.\n\n   Clone this repository inside an empty directory, so that the build can\n   safely make clones of repositories it depends on as _peers_ of `ethereal`.\n   Run `wrath -F` in the repository root. This will download and compile the\n   latest version of Scala, as well as all of Ethereal's dependencies.\n\n   If the build was successful, the compiled JAR files can be found in the\n   `.wrath/dist` directory.\n\n## Contributing\n\nContributors to Ethereal are welcome and encouraged. New contributors may like\nto look for issues marked\n[beginner](https://github.com/propensive/ethereal/labels/beginner).\n\nWe suggest that all contributors read the [Contributing\nGuide](/contributing.md) to make the process of contributing to Ethereal\neasier.\n\nPlease __do not__ contact project maintainers privately with questions unless\nthere is a good reason to keep them private. While it can be tempting to\nrepsond to such questions, private answers cannot be shared with a wider\naudience, and it can result in duplication of effort.\n\n## Author\n\nEthereal was designed and developed by Jon Pretty, and commercial support and\ntraining on all aspects of Scala 3 is available from [Propensive\nO\u0026Uuml;](https://propensive.com/).\n\n\n\n## Name\n\n_Ethereal_ alludes to the present, but unseen—and thus daemons.\n\nIn general, Soundness project names are always chosen with some rationale,\nhowever it is usually frivolous. Each name is chosen for more for its\n_uniqueness_ and _intrigue_ than its concision or catchiness, and there is no\nbias towards names with positive or \"nice\" meanings—since many of the libraries\nperform some quite unpleasant tasks.\n\nNames should be English words, though many are obscure or archaic, and it\nshould be noted how willingly English adopts foreign words. Names are generally\nof Greek or Latin origin, and have often arrived in English via a romance\nlanguage.\n\n## Logo\n\nThe logo shows a spectrum of light, and is thus _ethereal_.\n\n## License\n\nEthereal is copyright \u0026copy; 2025 Jon Pretty \u0026 Propensive O\u0026Uuml;, and\nis made available under the [Apache 2.0 License](/license.md).\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpropensive%2Fethereal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpropensive%2Fethereal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpropensive%2Fethereal/lists"}