diff options
author | Ben Sima <ben@bsima.me> | 2020-12-04 11:16:25 -0500 |
---|---|---|
committer | Ben Sima <ben@bsima.me> | 2020-12-05 07:55:13 -0500 |
commit | 330e4363d8abb509031d2c8c1a89dcc6f955e2c1 (patch) | |
tree | 915c8c50a7125bf6eb9e560f8d00a80592f41c77 /Que | |
parent | 32f53350a3a3d701e9a1474e670a8454342adc40 (diff) |
Renamespace Devalloc and Que
Move them under the Biz root so that we know they are specific to Biz stuff. Biz
is for proprietary stuff that we own.
I also had to refactor the bild namespace parsing code because it couldn't
handle a namespace with 3 parts. I really need to get that namespace library
written and tested.
Diffstat (limited to 'Que')
-rw-r--r-- | Que/Apidocs.md | 3 | ||||
-rwxr-xr-x | Que/Client.py | 186 | ||||
-rw-r--r-- | Que/Host.hs | 254 | ||||
-rw-r--r-- | Que/Host.nix | 46 | ||||
-rw-r--r-- | Que/Index.md | 73 | ||||
-rw-r--r-- | Que/Prod.nix | 61 | ||||
-rw-r--r-- | Que/Quescripts.md | 50 | ||||
-rw-r--r-- | Que/Site.hs | 135 | ||||
-rw-r--r-- | Que/Site.nix | 61 | ||||
-rw-r--r-- | Que/Style.css | 136 | ||||
-rw-r--r-- | Que/Tutorial.md | 53 |
11 files changed, 0 insertions, 1058 deletions
diff --git a/Que/Apidocs.md b/Que/Apidocs.md deleted file mode 100644 index f400889..0000000 --- a/Que/Apidocs.md +++ /dev/null @@ -1,3 +0,0 @@ -% que.run Api Docs - -coming soon diff --git a/Que/Client.py b/Que/Client.py deleted file mode 100755 index 1063eb8..0000000 --- a/Que/Client.py +++ /dev/null @@ -1,186 +0,0 @@ -#!/usr/bin/env python3 -""" -simple client for que.run -""" - -import argparse -import configparser -import functools -import http.client -import logging -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): - "Returns the auth key for the given ns from ~/.config/que.conf" - logging.debug("auth") - namespace = args.target.split("/")[0] - if namespace == "pub": - return None - 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[namespace]["key"] - - -def autodecode(bytestring): - """Attempt to decode bytes `bs` 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> - - """ - logging.debug("autodecode") - codecs = ["utf-8", "ascii"] - for codec in codecs: - try: - return bytestring.decode(codec) - except UnicodeDecodeError: - pass - return bytestring - - -def retry(exception, tries=4, delay=3, backoff=2): - "Decorator for retrying an action." - - def decorator(func): - @functools.wraps(func) - def func_retry(*args, **kwargs): - mtries, mdelay = tries, delay - while mtries > 1: - try: - return func(*args, **kwargs) - except exception as ex: - logging.debug(ex) - logging.debug("retrying...") - time.sleep(mdelay) - mtries -= 1 - mdelay *= backoff - return func(*args, **kwargs) - - return func_retry - - return decorator - - -def send(args): - "Send a message to the que." - logging.debug("send") - 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: - logging.debug("serve") - while not time.sleep(1): - request.urlopen(req, data=data, timeout=MAX_TIMEOUT) - - else: - request.urlopen(req, data=data, timeout=MAX_TIMEOUT) - - -def then(args, msg): - "Perform an action when passed `--then`." - if args.then: - logging.debug("then") - subprocess.run( - args.then.format(msg=msg, que=args.target), check=False, shell=True, - ) - - -@retry(http.client.IncompleteRead, tries=10, delay=5, backoff=1) -@retry(http.client.RemoteDisconnected, tries=10, delay=2, backoff=2) -def recv(args): - "Receive a message from the que." - logging.debug("recv on: %s", args.target) - 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: - logging.debug("poll") - while not time.sleep(1): - logging.debug("reading") - msg = autodecode(_req.readline()) - logging.debug("read") - print(msg, end="") - then(args, msg) - else: - msg = autodecode(_req.read()) - print(msg) - then(args, msg) - - -def get_args(): - "Command line parser" - cli = argparse.ArgumentParser(description=__doc__) - cli.add_argument("--debug", action="store_true", help="log to stderr") - 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," - r"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__": - ARGV = get_args() - if ARGV.debug: - logging.basicConfig( - format="%(asctime)s %(message)s", - level=logging.DEBUG, - datefmt="%Y.%m.%d..%H.%M.%S", - ) - try: - if ARGV.infile: - send(ARGV) - else: - recv(ARGV) - except KeyboardInterrupt: - sys.exit(0) diff --git a/Que/Host.hs b/Que/Host.hs deleted file mode 100644 index b8e7a1a..0000000 --- a/Que/Host.hs +++ /dev/null @@ -1,254 +0,0 @@ -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE NoImplicitPrelude #-} - --- | 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/> --- --- : out que-server --- --- : dep async --- : dep envy --- : dep protolude --- : dep scotty --- : dep stm --- : dep unagi-chan --- : dep unordered-containers -module Que.Host - ( main, - ) -where - -import Alpha hiding (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 qualified Data.Text.Lazy as Text.Lazy -import qualified Data.Text.Lazy.IO as Text.Lazy.IO -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 Web.Scotty.Trans as Scotty -import qualified Prelude - -{-# ANN module ("HLint: ignore Reduce duplication" :: Prelude.String) #-} - -main :: IO () -main = Exception.bracket startup shutdown <| uncurry Warp.run - where - startup = Envy.decodeWithDefaults Envy.defConfig >>= \c -> do - sync <- STM.newTVarIO initialAppState - let runActionToIO m = runReaderT (runApp m) sync - waiapp <- Scotty.scottyAppT runActionToIO <| routes c - putText "*" - putText "Que.Host" - putText <| "port: " <> (show <| quePort c) - putText <| "skey: " <> (show <| queSkey 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) - ) - -newtype AppState - = AppState - { ques :: HashMap Namespace Quebase - } - -initialAppState :: AppState -initialAppState = AppState {ques = mempty} - -data Config - = Config - { -- | QUE_PORT - quePort :: Warp.Port, - -- | QUE_SKEY - queSkey :: FilePath - } - deriving (Generic, Show) - -instance Envy.DefConfig Config where - defConfig = Config 3000 "/run/skey/que-admin" - -instance Envy.FromEnv Config - -routes :: Config -> Scotty.ScottyT Text.Lazy.Text App () -routes cfg = 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.Lazy.IO.readFile (queSkey cfg) - 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 - guardNs ns ["pub", "_"] - app . modify <| upsertNamespace ns - q <- app <| que ns qp - poll <- Scotty.param "poll" !: (pure . const False) - if poll - then Scotty.stream <| streamQue q - else 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.Lazy.IO.readFile (queSkey cfg) - (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.Lazy.Text -> [Text.Lazy.Text] -> Scotty.ActionT Text.Lazy.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. -(!:) :: - -- | action that might throw - Scotty.ActionT Text.Lazy.Text App a -> - -- | a function providing a default response instead - (Text.Lazy.Text -> Scotty.ActionT Text.Lazy.Text App a) -> - Scotty.ActionT Text.Lazy.Text App a -(!:) = Scotty.rescue - --- | Forever write the data from 'Que' to 'Wai.StreamingBody'. -streamQue :: Que -> Wai.StreamingBody -streamQue q write _ = loop q - where - loop c = - Go.read c - >>= (write . Builder.byteStringInsert) - >> 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.Lazy.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 - --- | housing for a set of que paths -type Namespace = Text.Lazy.Text - --- | a que is just a channel of bytes -type Que = Go.Channel Message - --- | any path can serve as an identifier for a que -type Quepath = Text - --- | any opaque data -type Message = ByteString - --- | a collection of ques -type Quebase = HashMap Quepath Que - --- | 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/Que/Host.nix b/Que/Host.nix deleted file mode 100644 index e326483..0000000 --- a/Que/Host.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/Que/Index.md b/Que/Index.md deleted file mode 100644 index a9db12e..0000000 --- a/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/Que/Prod.nix b/Que/Prod.nix deleted file mode 100644 index b755d7c..0000000 --- a/Que/Prod.nix +++ /dev/null @@ -1,61 +0,0 @@ -{ bild, lib }: - -# The production server for que.run - -bild.os { - imports = [ - ../Biz/OsBase.nix - ../Biz/Packages.nix - ../Biz/Users.nix - ./Host.nix - ./Site.nix - ]; - networking.hostName = "prod-que"; - networking.domain = "que.run"; - services.que-server = { - enable = true; - port = 80; - package = bild.ghc ./Host.hs; - }; - boot.loader.grub.device = "/dev/vda"; - fileSystems."/" = { device = "/dev/vda1"; fsType = "ext4"; }; - swapDevices = [ - { device = "/swapfile"; } # 4GB - ]; - networking.firewall.allowedTCPPorts = [ 22 80 443 ]; - 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 = { - que-website = { - enable = true; - namespace = "_"; - package = bild.ghc ./Site.hs; - }; - - udev.extraRules = '' - ATTR{address}=="7a:92:a5:c6:db:c3", NAME="eth0" - ''; - }; -} diff --git a/Que/Quescripts.md b/Que/Quescripts.md deleted file mode 100644 index 77e7004..0000000 --- a/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 something like this (just watch your quotes): - - 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/Que/Site.hs b/Que/Site.hs deleted file mode 100644 index 5d2dbb8..0000000 --- a/Que/Site.hs +++ /dev/null @@ -1,135 +0,0 @@ -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE NoImplicitPrelude #-} - --- | spawns a few processes that serve the que.run website --- --- : out que-website --- --- : dep async --- : dep config-ini --- : dep process --- : dep protolude --- : dep req -module Que.Site - ( 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 |> void - 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/Que/Site.nix b/Que/Site.nix deleted file mode 100644 index ba2eeb2..0000000 --- a/Que/Site.nix +++ /dev/null @@ -1,61 +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/Que/Style.css b/Que/Style.css deleted file mode 100644 index f8d1ca4..0000000 --- a/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/Que/Tutorial.md b/Que/Tutorial.md deleted file mode 100644 index 6542ad3..0000000 --- a/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}'" |