summaryrefslogtreecommitdiff
path: root/Run/Que
diff options
context:
space:
mode:
authorBen Sima <ben@bsima.me>2020-04-15 09:54:10 -0700
committerBen Sima <ben@bsima.me>2020-04-15 10:06:56 -0700
commitf4b8c0df041b063c0b47d2ec6c818a9c202fd833 (patch)
tree01ad246a83fda29c079847b3397ca6509a7f6106 /Run/Que
parent6ed475ca94209ce92e75f48764cb9d361029ea26 (diff)
Re-namespacing
Moving away from the DNS-driven namespacing toward more condensed names, mostly because I don't like typing so much.
Diffstat (limited to 'Run/Que')
-rw-r--r--Run/Que/Prod.nix44
-rw-r--r--Run/Que/Server.hs240
-rw-r--r--Run/Que/Server.nix46
-rw-r--r--Run/Que/Website.hs126
-rw-r--r--Run/Que/Website.nix59
-rw-r--r--Run/Que/apidocs.md3
-rwxr-xr-xRun/Que/client.py149
-rw-r--r--Run/Que/index.md73
-rw-r--r--Run/Que/quescripts.md50
-rw-r--r--Run/Que/style.css136
-rw-r--r--Run/Que/tutorial.md53
11 files changed, 0 insertions, 979 deletions
diff --git a/Run/Que/Prod.nix b/Run/Que/Prod.nix
deleted file mode 100644
index 97749c8..0000000
--- a/Run/Que/Prod.nix
+++ /dev/null
@@ -1,44 +0,0 @@
-{ config, pkgs, lib, ... }:
-{
- imports = [ <nixpkgs/nixos/modules/profiles/qemu-guest.nix> ];
- boot.loader.grub.device = "/dev/vda";
- fileSystems."/" = { device = "/dev/vda1"; fsType = "ext4"; };
- networking.firewall.allowedTCPPorts = [ 22 80 443 ];
- services.que-server = {
- enable = true;
- port = 80;
- package = pkgs.que-server;
- };
- services.que-website = {
- enable = true;
- namespace = "_";
- package = pkgs.que-website;
- };
- networking = {
- nameservers = [
- "67.207.67.2"
- "67.207.67.3"
- ];
- defaultGateway = "157.245.224.1";
- defaultGateway6 = "2604:a880:2:d1::1";
- dhcpcd.enable = false;
- usePredictableInterfaceNames = lib.mkForce true;
- interfaces = {
- eth0 = {
- ipv4.addresses = [
- { address="157.245.236.44"; prefixLength=20; }
- { address="10.46.0.5"; prefixLength=16; }
- ];
- ipv6.addresses = [
- { address="2604:a880:2:d1::a2:5001"; prefixLength=64; }
- { address="fe80::7892:a5ff:fec6:dbc3"; prefixLength=64; }
- ];
- ipv4.routes = [ { address = "157.245.224.1"; prefixLength = 32; } ];
- ipv6.routes = [ { address = "2604:a880:2:d1::1"; prefixLength = 32; } ];
- };
- };
- };
- services.udev.extraRules = ''
- ATTR{address}=="7a:92:a5:c6:db:c3", NAME="eth0"
- '';
-}
diff --git a/Run/Que/Server.hs b/Run/Que/Server.hs
deleted file mode 100644
index 0fc9fd1..0000000
--- a/Run/Que/Server.hs
+++ /dev/null
@@ -1,240 +0,0 @@
-{-# LANGUAGE NoImplicitPrelude #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE LambdaCase #-}
-{-# LANGUAGE DeriveGeneric #-}
-
--- | Interprocess communication
---
--- Prior art:
--- - <https://github.com/jb55/httpipe>
--- - <https://patchbay.pub>
--- - <https://github.com/hargettp/courier>
--- - sorta: <https://ngrok.com/> and <https://localtunnel.github.io/www/>
---
--- : exe que-server
---
--- : dep async
--- : dep envy
--- : dep protolude
--- : dep scotty
--- : dep stm
--- : dep unagi-chan
--- : dep unordered-containers
-module Run.Que.Server
- ( main
- )
-where
-
-import Alpha hiding ( Text
- , get
- , gets
- , modify
- , poll
- )
-import qualified Control.Concurrent.Go as Go
-import qualified Control.Concurrent.STM as STM
-import qualified Control.Exception as Exception
-import Control.Monad.Reader ( MonadTrans )
-import qualified Data.ByteString.Builder.Extra as Builder
-import qualified Data.ByteString.Lazy as BSL
-import Data.HashMap.Lazy ( HashMap )
-import qualified Data.HashMap.Lazy as HashMap
-import qualified Data.Text.Encoding as Encoding
-import Data.Text.Lazy ( Text
- , fromStrict
- )
-import qualified Data.Text.Lazy.IO as Text
-import qualified Network.HTTP.Types.Status as Http
-import qualified Network.Wai as Wai
-import qualified Network.Wai.Handler.Warp as Warp
-import Network.Wai.Middleware.RequestLogger
- ( logStdout )
-import qualified System.Envy as Envy
-import qualified System.Exit as Exit
-import qualified Web.Scotty.Trans as Scotty
-
-main :: IO ()
-main = Exception.bracket startup shutdown <| uncurry Warp.run
- where
- startup = Envy.decodeEnv >>= \case
- Left e -> Exit.die e
- Right c -> do
- sync <- STM.newTVarIO initialAppState
- let runActionToIO m = runReaderT (runApp m) sync
- waiapp <- Scotty.scottyAppT runActionToIO routes
- putText <| "port:" <> (show <| quePort c)
- return (quePort c, waiapp)
- shutdown :: a -> IO a
- shutdown = pure . identity
-
-newtype App a = App
- { runApp :: ReaderT (STM.TVar AppState) IO a
- }
- deriving (Applicative, Functor, Monad, MonadIO, MonadReader
- (STM.TVar AppState))
-
-data AppState = AppState
- { ques :: HashMap Namespace Quebase
- }
-
-initialAppState :: AppState
-initialAppState = AppState { ques = mempty }
-
-data Config = Config
- { quePort :: Warp.Port -- ^ QUE_PORT
- } deriving (Generic, Show)
-
-instance Envy.DefConfig Config where
- defConfig = Config 3000
-
-instance Envy.FromEnv Config
-
-routes :: Scotty.ScottyT Text App ()
-routes = do
- Scotty.middleware logStdout
-
- let quepath = "^\\/([[:alnum:]_]+)\\/([[:alnum:]._/]*)$"
- let namespace = "^\\/([[:alnum:]_]+)\\/?$" -- matches '/ns' and '/ns/' but not '/ns/path'
-
- -- | GET /index.html
- Scotty.get (Scotty.literal "/index.html") <| Scotty.redirect "/_/index"
- Scotty.get (Scotty.literal "/") <| Scotty.redirect "/_/index"
-
- -- | GET /_/dash
- Scotty.get (Scotty.literal "/_/dash") <| do
- authkey <- fromMaybe "" </ Scotty.header "Authorization"
- adminkey <- liftIO <| lchomp </ Text.readFile "/run/keys/que-admin"
- if authkey == adminkey
- then do
- d <- app <| gets ques
- Scotty.json d
- else do
- Scotty.status Http.methodNotAllowed405
- Scotty.text "not allowed"
-
-
- -- | Namespace management
- Scotty.matchAny (Scotty.regex namespace) <| do
- Scotty.status Http.notImplemented501
- Scotty.text "namespace management coming soon"
-
- -- | GET que
- --
- -- Receive a value from a que. Blocks until a value is received,
- -- then returns. If 'poll=true', then stream data from the Que to the
- -- client.
- Scotty.get (Scotty.regex quepath) <| do
- (ns, qp) <- extract
- app . modify <| upsertNamespace ns
- q <- app <| que ns qp
- poll <- Scotty.param "poll" !: (pure . const False)
- guardNs ns ["pub", "_"]
- case poll of
- True -> Scotty.stream $ streamQue q
- _ -> do
- r <- liftIO <| Go.read q
- Scotty.html <| fromStrict <| Encoding.decodeUtf8 r
-
- -- | POST que
- --
- -- Put a value on a que. Returns immediately.
- Scotty.post (Scotty.regex quepath) <| do
- authkey <- fromMaybe "" </ Scotty.header "Authorization"
- adminkey <- liftIO <| lchomp </ Text.readFile "/run/keys/que-admin"
- (ns, qp) <- extract
- -- Only allow my IP or localhost to publish to '_' namespace
- when ("_" == ns && authkey /= adminkey)
- <| Scotty.status Http.methodNotAllowed405
- >> Scotty.text "not allowed: _ is a reserved namespace"
- >> Scotty.finish
- guardNs ns ["pub", "_"]
- -- passed all auth checks
- app . modify <| upsertNamespace ns
- q <- app <| que ns qp
- qdata <- Scotty.body
- _ <- liftIO <| Go.write q <| BSL.toStrict qdata
- return ()
-
--- | Given `guardNs ns whitelist`, if `ns` is not in the `whitelist`
--- list, return a 405 error.
-guardNs :: Text -> [Text] -> Scotty.ActionT Text App ()
-guardNs ns whitelist = when (not <| ns `elem` whitelist) <| do
- Scotty.status Http.methodNotAllowed405
- Scotty.text
- <| "not allowed: use 'pub' namespace or signup to protect '"
- <> ns
- <> "' at https://que.run"
- Scotty.finish
-
--- | recover from a scotty-thrown exception.
-(!:)
- :: Scotty.ActionT Text App a -- ^ action that might throw
- -> (Text -> Scotty.ActionT Text App a) -- ^ a function providing a default response instead
- -> Scotty.ActionT Text App a
-(!:) = Scotty.rescue
-
--- | Forever write the data from 'Que' to 'Wai.StreamingBody'.
-streamQue :: Que -> Wai.StreamingBody
-streamQue q write _ = Go.mult q >>= loop
- where
- loop c =
- Go.tap c
- >>= (write . Builder.byteStringInsert)
- >> (write <| Builder.byteStringInsert "\n")
- >> loop c
-
--- | Gets the thing from the Hashmap. Call's 'error' if key doesn't exist.
-grab :: (Eq k, Hashable k) => k -> HashMap k v -> v
-grab = flip (HashMap.!)
-
--- | Inserts the namespace in 'AppState' if it doesn't exist.
-upsertNamespace :: Namespace -> AppState -> AppState
-upsertNamespace ns as = if HashMap.member ns (ques as)
- then as
- else as { ques = HashMap.insert ns mempty (ques as) }
-
--- | Inserts the que at the proper 'Namespace' and 'Quepath'.
-insertQue :: Namespace -> Quepath -> Que -> AppState -> AppState
-insertQue ns qp q as = as { ques = newQues }
- where
- newQues = HashMap.insert ns newQbase (ques as)
- newQbase = HashMap.insert qp q <| grab ns <| ques as
-
-extract :: Scotty.ActionT Text App (Namespace, Quepath)
-extract = do
- ns <- Scotty.param "1"
- path <- Scotty.param "2"
- return (ns, path)
-
--- | A synonym for 'lift' in order to be explicit about when we are
--- operating at the 'App' layer.
-app :: MonadTrans t => App a -> t App a
-app = lift
-
--- | Get something from the app state
-gets :: (AppState -> b) -> App b
-gets f = ask >>= liftIO . STM.readTVarIO >>= return . f
-
--- | Apply a function to the app state
-modify :: (AppState -> AppState) -> App ()
-modify f = ask >>= liftIO . atomically . flip STM.modifyTVar' f
-
-type Namespace = Text -- ^ housing for a set of que paths
-type Que = Go.Channel Message -- ^ a que is just a channel of bytes
-type Quepath = Text -- ^ any path can serve as an identifier for a que
-type Message = ByteString -- ^ any opaque data
-type Quebase = HashMap Quepath Que -- ^ a collection of ques
-
--- | Lookup or create a que
-que :: Namespace -> Quepath -> App Que
-que ns qp = do
- _ques <- gets ques
- let qbase = grab ns _ques
- queExists = HashMap.member qp qbase
- if queExists
- then return <| grab qp qbase
- else do
- c <- liftIO <| Go.chan 1
- modify (insertQue ns qp c)
- gets ques /> grab ns /> grab qp
diff --git a/Run/Que/Server.nix b/Run/Que/Server.nix
deleted file mode 100644
index e326483..0000000
--- a/Run/Que/Server.nix
+++ /dev/null
@@ -1,46 +0,0 @@
-{ options
-, lib
-, config
-, pkgs
-, modulesPath
-}:
-
-let
- cfg = config.services.que-server;
-in
-{
- options.services.que-server = {
- enable = lib.mkEnableOption "Enable the que-server service";
- port = lib.mkOption {
- type = lib.types.int;
- default = 3000;
- description = ''
- The port on which que-server will listen for
- incoming HTTP traffic.
- '';
- };
- package = lib.mkOption {
- type = lib.types.package;
- description = "que-server package to use";
- };
- };
- config = lib.mkIf cfg.enable {
- systemd.services.que-server = {
- path = [ cfg.package ];
- wantedBy = [ "multi-user.target" ];
- script = ''
- ${cfg.package}/bin/que-server
- '';
- description = ''
- Que server
- '';
- serviceConfig = {
- Environment = ["QUE_PORT=${toString cfg.port}"];
- KillSignal = "INT";
- Type = "simple";
- Restart = "on-abort";
- RestartSec = "1";
- };
- };
- };
-}
diff --git a/Run/Que/Website.hs b/Run/Que/Website.hs
deleted file mode 100644
index 52e46f9..0000000
--- a/Run/Que/Website.hs
+++ /dev/null
@@ -1,126 +0,0 @@
-{-# LANGUAGE RecordWildCards #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE NoImplicitPrelude #-}
-{-# LANGUAGE LambdaCase #-}
-
--- | spawns a few processes that serve the que.run website
---
--- : exe que-website
---
--- : dep async
--- : dep config-ini
--- : dep process
--- : dep protolude
--- : dep req
-module Run.Que.Website
- ( main
- )
-where
-
-import Alpha
-import qualified Control.Concurrent.Async as Async
-import qualified Data.ByteString.Char8 as BS
-import qualified Data.Ini.Config as Config
-import qualified Data.Text as Text
-import Data.Text.Encoding ( encodeUtf8 )
-import qualified Data.Text.IO as Text
-import Network.HTTP.Req
-import qualified System.Directory as Directory
-import System.Environment as Environment
-import qualified System.Exit as Exit
-import System.FilePath ( (</>) )
-import qualified System.Process as Process
-
-main :: IO ()
-main = do
- (src, ns) <- Environment.getArgs >>= \case
- [src] -> return (src, "_") -- default to _ ns which is special
- [src, ns] -> return (src, Text.pack ns)
- _ -> Exit.die "usage: que-website <srcdir> [namespace]"
- mKey <- getKey ns
- putText $ "serving " <> Text.pack src <> " at " <> ns
- run mKey ns $ Sources { index = src </> "index.md"
- , client = src </> "client.py"
- , quescripts = src </> "quescripts.md"
- , style = src </> "style.css"
- , apidocs = src </> "apidocs.md"
- , tutorial = src </> "tutorial.md"
- }
-
-getKey :: Namespace -> IO (Maybe Key)
-getKey ns = do
- home <- Directory.getHomeDirectory
- let file = home </> ".config" </> "que.conf"
- exists <- (Directory.doesFileExist file)
- unless exists <| panic <| "not found: " <> Text.pack file
- conf <- Text.readFile file
- print (home </> ".config" </> "que.conf")
- auth ns
- |> Config.parseIniFile conf
- |> either errorParsingConf identity
- |> return
-
-errorParsingConf :: error
-errorParsingConf = panic "could not parse ~/.config/que.conf"
-
-data Sources = Sources
- { index :: FilePath
- , quescripts :: FilePath
- , client :: FilePath
- , style :: FilePath
- , tutorial :: FilePath
- , apidocs :: FilePath
- }
-
-type Namespace = Text
-type Key = Text
-
-auth :: Namespace -> Config.IniParser (Maybe Key)
-auth "pub" = pure Nothing
-auth ns = Config.sectionMb ns <| Config.field "key"
-
-run :: Maybe Key -> Text -> Sources -> IO ()
-run key ns Sources {..} = Async.runConcurrently actions >> return ()
- where
- actions = traverse
- Async.Concurrently
- [ forever <| toHtml index >>= serve key ns "index"
- , forever <| toHtml quescripts >>= serve key ns "quescripts"
- , forever <| BS.readFile client >>= serve key ns "client"
- , forever <| toHtml tutorial >>= serve key ns "tutorial"
- , forever <| toHtml apidocs >>= serve key ns "apidocs"
- ]
- toHtml :: FilePath -> IO ByteString
- toHtml md =
- BS.pack
- <$> Process.readProcess
- "pandoc"
- [ "--include-in-header"
- , style
- , "-i"
- , md
- , "--from"
- , "markdown"
- , "--to"
- , "html"
- ]
- []
-
-serve :: Maybe Key -> Namespace -> Text -> ByteString -> IO ()
-serve Nothing "pub" path content = runReq defaultHttpConfig $ do
- _ <- req POST
- (http "que.run" /: "pub" /: path)
- (ReqBodyBs content)
- ignoreResponse
- mempty
- liftIO $ return ()
-serve Nothing p _ _ = panic <| "no auth key provided for ns: " <> p
-serve (Just key) ns path content = runReq defaultHttpConfig $ do
- let options =
- header "Authorization" (encodeUtf8 key) <> responseTimeout maxBound
- _ <- req POST
- (http "que.run" /: ns /: path)
- (ReqBodyBs content)
- ignoreResponse
- options
- liftIO $ return ()
diff --git a/Run/Que/Website.nix b/Run/Que/Website.nix
deleted file mode 100644
index 6a24d9d..0000000
--- a/Run/Que/Website.nix
+++ /dev/null
@@ -1,59 +0,0 @@
-{ options
-, lib
-, config
-, pkgs
-, modulesPath
-}:
-
-let
- cfg = config.services.que-website;
- static = pkgs.stdenv.mkDerivation {
- src = ./.;
- name = "que-website-static";
- installPhase = ''
- mkdir -p $out
- cp ${./apidocs.md} $out/apidocs.md
- cp ${./index.md} $out/index.md
- cp ${./quescripts.md} $out/quescripts.md
- cp ${./style.css} $out/style.css
- cp ${./tutorial.md} $out/tutorial.md
- cp ${./client.py} $out/client.py
- '';
- };
-in
-{
- options.services.que-website = {
- enable = lib.mkEnableOption "Enable the que-website service";
- namespace = lib.mkOption {
- type = lib.types.str;
- default = "_";
- description = ''
- The que namespace on which que-website will broadcast.
- '';
- };
- package = lib.mkOption {
- type = lib.types.package;
- description = "que-website package to use";
- };
- };
- config = lib.mkIf cfg.enable {
- systemd.services.que-website = {
- path = [ cfg.package pkgs.pandoc ];
- wantedBy = [ "multi-user.target" ];
- script = ''
- ${cfg.package}/bin/que-website ${static} ${cfg.namespace}
- '';
- description = ''
- Que website server
- '';
- serviceConfig = {
- User = "root";
- Environment = "HOME=/root";
- KillSignal = "INT";
- Type = "simple";
- Restart = "on-abort";
- RestartSec = "1";
- };
- };
- };
-}
diff --git a/Run/Que/apidocs.md b/Run/Que/apidocs.md
deleted file mode 100644
index f400889..0000000
--- a/Run/Que/apidocs.md
+++ /dev/null
@@ -1,3 +0,0 @@
-% que.run Api Docs
-
-coming soon
diff --git a/Run/Que/client.py b/Run/Que/client.py
deleted file mode 100755
index 3d9291d..0000000
--- a/Run/Que/client.py
+++ /dev/null
@@ -1,149 +0,0 @@
-#!/usr/bin/env python3
-"""
-simple client for que.run
-"""
-
-import argparse
-import configparser
-import http.client
-import os
-import subprocess
-import sys
-import time
-import urllib.parse
-import urllib.request as request
-
-MAX_TIMEOUT = 99999999 # basically never timeout
-
-
-def auth(args):
- ns = args.target.split("/")[0]
- if ns == "pub":
- return None
- else:
- conf_file = os.path.expanduser("~/.config/que.conf")
- if not os.path.exists(conf_file):
- sys.exit("you need a ~/.config/que.conf")
- cfg = configparser.ConfigParser()
- cfg.read(conf_file)
- return cfg[ns]["key"]
-
-
-def send(args):
- "Send a message to the que."
- key = auth(args)
- data = args.infile
- req = request.Request(f"{args.host}/{args.target}")
- req.add_header("User-AgenT", "Que/Client")
- if key:
- req.add_header("Authorization", key)
- if args.serve:
- while not time.sleep(1):
- request.urlopen(req, data=data, timeout=MAX_TIMEOUT)
-
- else:
- request.urlopen(req, data=data, timeout=MAX_TIMEOUT)
-
-
-def recv(args):
- "Receive a message from the que."
-
- def _recv(_req):
- msg = autodecode(_req.read())
- print(msg)
- if args.then:
- subprocess.run(
- args.then.replace("\msg", msg).replace("\que", args.target), shell=True
- )
-
- params = urllib.parse.urlencode({"poll": args.poll})
- req = request.Request(f"{args.host}/{args.target}?{params}")
- req.add_header("User-Agent", "Que/Client")
- key = auth(args)
- if key:
- req.add_header("Authorization", key)
- with request.urlopen(req) as _req:
- if args.poll:
- while not time.sleep(1):
- _recv(_req)
- else:
- _recv(_req)
-
-
-def autodecode(b):
- """Attempt to decode bytes `b` into common codecs, preferably utf-8. If
- no decoding is available, just return the raw bytes.
-
- For all available codecs, see:
- <https://docs.python.org/3/library/codecs.html#standard-encodings>
-
- """
- codecs = ["utf-8", "ascii"]
- for codec in codecs:
- try:
- return b.decode(codec)
- except UnicodeDecodeError:
- pass
- return b
-
-
-def get_args():
- cli = argparse.ArgumentParser(description=__doc__)
- cli.add_argument(
- "--host", default="http://que.run", help="where que-server is running"
- )
- cli.add_argument(
- "--poll", default=False, action="store_true", help="stream data from the que"
- )
- cli.add_argument(
- "--then",
- help=" ".join(
- [
- "when polling, run this shell command after each response,",
- "presumably for side effects,"
- "replacing '\que' with the target and '\msg' with the body of the response",
- ]
- ),
- )
- cli.add_argument(
- "--serve",
- default=False,
- action="store_true",
- help=" ".join(
- [
- "when posting to the que, do so continuously in a loop.",
- "this can be used for serving a webpage or other file continuously",
- ]
- ),
- )
- cli.add_argument(
- "target", help="namespace and path of the que, like 'ns/path/subpath'"
- )
- cli.add_argument(
- "infile",
- nargs="?",
- type=argparse.FileType("rb"),
- help="data to put on the que. Use '-' for stdin, otherwise should be a readable file",
- )
- return cli.parse_args()
-
-
-if __name__ == "__main__":
- args = get_args()
- try:
- if args.infile:
- send(args)
- else:
- recv(args)
- except KeyboardInterrupt:
- sys.exit(0)
- except urllib.error.HTTPError as e:
- print(e)
- sys.exit(1)
- except http.client.RemoteDisconnected as e:
- print("disconnected... retrying in 5 seconds")
- time.sleep(5)
- if args.infile:
- send(args)
- else:
- recv(args)
diff --git a/Run/Que/index.md b/Run/Que/index.md
deleted file mode 100644
index a9db12e..0000000
--- a/Run/Que/index.md
+++ /dev/null
@@ -1,73 +0,0 @@
-% que.run
-
-que.run is the concurrent, async runtime in the cloud
-
- - runtime concurrency anywhere you have a network connection
- - multilanguage communicating sequential processes
- - add Go-like channels to any language
- - connect your microservices together with the simplest possible
- plumbing
- - async programming as easy as running two terminal commands
-
-HTTP routes on `que.run` are Golang-like channels with a namespace and a
-path. For example: `https://que.run/pub/path/subpath`.
-
-## Quickstart
-
-There is a simple script `que` that acts as a client you can use to
-interact with the `que.run` service.
-
-Download it to somewhere on your `$PATH` and make it executable:
-
- curl https://que.run/_/client > ~/bin/que
- chmod +x ~/bin/que
- que --help
-
-The client requires a recent version of Python 3.
-
-## Powerup
-
-que.run is free for limited use, but the real power of an asynchronous,
-concurrent runtime in the cloud is unlocked with some extra power-user
-features.
-
-- Free
- - security by obscurity
- - all protocols and data formats supported
- - bandwidth and message sizes limited
- - concurrent connections limited
- - request rate limited
-- Power
- - protect your data with private namespaces
- - remove bandwidth and size limits
- - private dashboard to see all of your active ques
- - 99.999% uptime
-- Pro
- - add durability to your ques so messages are never lost
- - powerful batch api
- - incredible query api
- - Linux FUSE filesystem integration
-- Enterprise
- - all of the Power & Pro features
- - on-prem deployment
- - advanced que performance monitoring
- - SLA for support from que.run experts
-
-Email `ben@bsima.me` if you want to sign up for the Power, Pro, or
-Enterprise packages.
-
-## Quescripts
-
-We are collecting a repository of scripts that make awesome use of que:
-
-- remote desktop notifications
-- two-way communication with your phone
-- ephemeral, serverless chat rooms
-- collaborative jukebox
-
-<a id="quescripts-btn" href="/_/quescripts">See the scripts</a>
-
-## Docs
-
-- [tutorial](/_/tutorial)
-- [api docs](/_/apidocs)
diff --git a/Run/Que/quescripts.md b/Run/Que/quescripts.md
deleted file mode 100644
index 9a2e6e0..0000000
--- a/Run/Que/quescripts.md
+++ /dev/null
@@ -1,50 +0,0 @@
-% Quescripts
-
-## Remote desktop notifications
-
-Lets say we are running a job that takes a long time, maybe we are
-compiling or running a large test suite. Instead of watching the
-terminal until it completes, or flipping back to check on it every so
-often, we can create a listener that displays a popup notification when
-the job finishes.
-
-In one terminal run the listener:
-
- que pub/notify --then "notify-send '\que' '\msg'"
-
-In some other terminal run the job that takes forever:
-
- runtests ; echo "tests are done" | que pub/notify -
-
-When terminal 2 succeeds, terminal 1 will print "tests are done", then
-call the `notify-send` command, which displays a notification toast in
-Linux with title "`pub/notify`" and content "`tests are done`".
-
-Que paths are multi-producer and multi-consumer, so you can add as many
-terminals as you want.
-
-On macOS you could use:
-
- osascript -e 'display notification "\msg" with title "\que"'
-
-in place of notify-send.
-
-## Ephemeral, serverless chat rooms
-
-coming soon
-
-## Collaborative jukebox
-
-It's surprisingly easy to make a collaborative jukebox.
-
-First start up a music player:
-
- que --poll pub/music --then "playsong '\msg'"
-
-where `playsong` is a script that plays a file from data streaming to
-`stdin`. For example [vlc](https://www.videolan.org/vlc/) does this when
-you run it like `vlc -`.
-
-Then, anyone can submit songs with:
-
- que pub/music song.mp3
diff --git a/Run/Que/style.css b/Run/Que/style.css
deleted file mode 100644
index f8d1ca4..0000000
--- a/Run/Que/style.css
+++ /dev/null
@@ -1,136 +0,0 @@
-<link href="https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,400;0,700;1,400;1,700&family=Source+Sans+Pro:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet">
-<style>
-:root {
- /* base (http://chriskempson.com/projects/base16/) */
- --base00: #181818;
- --base01: #282828;
- --base02: #383838;
- --base03: #585858;
- --base04: #b8b8b8;
- --base05: #d8d8d8;
- --base06: #e8e8e8;
- --base07: #f8f8f8;
-
- /* highlights */
- --base08: #ab4642;
- --base09: #dc9656;
- --base0A: #f7ca88;
- --base0B: #a1b56c;
- --base0C: #86c1b9;
- --base0D: #7cafc2;
- --base0E: #ba8baf;
- --base0F: #a16946;
-}
-
-/* dark theme */
-@media ( prefers-color-scheme: dark ),
- ( prefers-color-scheme: no-preference )
-{
- body
- { color: var(--base05);
- ; background: var(--base00)
- }
-
- header, h1, h2, h3
- { color: var(--base0A) }
-
- a:link, a:visited
- { color: var(--base0D) }
-
- a:hover
- { color: var(--base0C) }
-
- pre
- { background-color: var(--base01) }
-
- code
- { color: var(--base0B)
- }
-
- hr
- { border: 0
- ; height: 1px
- ; width: 100%
- ; margin: 2rem
- ; background-image: linear-gradient(
- to right,
- /* same as --base0A */
- rgba(186, 139, 175, 0),
- rgba(186, 139, 175, 0.75),
- rgba(186, 139, 175, 0))
- }
-}
-
-/* light theme */
-
-@media ( prefers-color-scheme: light)
-{
- body
- { background-color: var(--base07)
- ; color: var(--base00)
- }
-
- a:link, a:visited
- { color: var(--base0D) }
-
- a:hover
- { color: var(--base0C) }
-
- pre
- { background-color: var(--base06) }
-
- code
- { color: var(--base0B) }
-}
-
-/* structure and layout */
-
-body
-{ max-width: 900px
-; margin: 40px auto
-; padding: 0 10px
-; font: 18px/1.5
- "Source Sans Pro",
- sans-serif,
- "Apple Color Emoji",
- "Segoe UI Emoji",
- "Segoe UI Symbol",
- "Noto Color Emoji"
-; display: flex
-; flex-direction: column
-; align-items: auto
-}
-
-header#title-block-header,
-h1,
-h2,
-h3
-{ line-height: 1.2
-; align-self: center
-; text-transform: lowercase
-}
-
-pre
-{ padding: .5rem }
-
-pre, code
-{ overflow-x: scroll
-; white-space: pre
-; font-family: "Source Code Pro", monospace;
-}
-
-#quescripts-btn
-{ border-width: 2px
-; border-style: solid
-}
-
-#quescripts-btn
-{ font-size: 1.2rem
-; padding: 1rem
-; text-decoration: none
-; text-align: center
-; display: block
-; max-width: 400px
-; margin: auto
-}
-</style>
diff --git a/Run/Que/tutorial.md b/Run/Que/tutorial.md
deleted file mode 100644
index 66ecd3c..0000000
--- a/Run/Que/tutorial.md
+++ /dev/null
@@ -1,53 +0,0 @@
-% que.run Tutorial
-
-## Ques
-
-A que is a multi-consumer, multi-producer channel available anywhere you
-have a network connection. If you are familiar with Go channels, they
-are pretty much the same thing. Put some values in one end, and take
-them out the other end at a different time, or in a different process.
-
-Ques are created dynamically for every HTTP request you make. Here we
-use the `que` client to create a new que at the path `pub/new-que`:
-
- que pub/new-que
-
-The `que` client is useful, but you can use anything to make the HTTP
-request, for example here's the same thing with curl:
-
- curl https://que.run/pub/new-que
-
-These requests will block until a value is placed on the other
-end. Let's do that now. In a separate terminal:
-
- echo "hello world" | que pub/new-que -
-
-This tells the `que` client to read the value from `stdin` and then send
-it to `example/new-que`. Or with curl:
-
- curl https://que.run/pub/new-que -d "hello world"
-
-This will succeed immediately and send the string "`hello world`" over
-the channel, which will be received and printed by the listener in the
-other terminal.
-
-You can have as many producers and consumers attached to a channel as
-you want.
-
-## Namespaces
-
-Ques are organized into namespaces, identified by the first fragment of
-the path. In the above commands we used `pub` as the namespace, which is
-a special publically-writable namespace. The other special namespace is
-`_` which is reserved for internal use only. You can't write to the `_`
-namespace.
-
-To use other namespaces and add authentication/access controls, you can
-[sign up for the Power package](/_/index).
-
-## Events
-
-Just reading and writing data isn't very exciting, so let's throw in
-some events. We can very quickly put together a job processor.
-
- que pub/new-que --then "./worker.sh '\msg'"