SASjs Server provides a NodeJS wrapper for calling the SAS binary executable. It can be installed on an actual SAS server, or locally on your desktop. It provides:
- Virtual filesystem for storing SAS programs and other content
- Ability to execute Stored Programs from a URL
- Ability to create web apps using simple Desktop SAS
- REST API with Swagger Docs
One major benefit of using SASjs Server alongside other components of the SASjs framework such as the CLI, Adapter and Core library, is that the projects you create can be very easily ported to SAS 9 (Stored Process server) or Viya (Job Execution server).
SASjs Server is available in two modes - Desktop (without authentication) and Server (with authentication, and a database)
Installation can be made programmatically using command line, or by manually downloading and running the executable.
Fetch the relevant package from github using curl, eg as follows (for linux):
curl -L https://github.com/sasjs/server/releases/latest/download/linux.zip > linux.zip
unzip linux.zipThe app can then be launched with ./api-linux and prompts followed (if ENV vars not set).
- Download the relevant package from the releases page
- Trigger by double clicking (windows) or executing from commandline.
You are presented with two prompts (if not set as ENV vars):
- Location of your
sas.exe/sas.shexecutable - Path to a filesystem location for Stored Programs and temporary files
A prebuilt container image is published as
ghcr.io/sasjs/server
with every release, usable anywhere Docker runs. It is built from this
repository's own source by the release workflow, so the image tag and the
release always refer to the same commit. See
container/README.md for the full container
documentation, and
container/docker-compose.yml for a complete
stack with MongoDB.
docker run -d --name sasjs \
-p 5000:5000 \
-v sasjs_data:/usr/server/data \
-e DB_CONNECT=mongodb://mongo:27017/sasjs \
ghcr.io/sasjs/serverServer mode (the default) is multi-user and needs a MongoDB reachable at
DB_CONNECT; set MODE=desktop for a single-user instance with no database.
The container is configured entirely by the environment variables documented
below and speaks plain HTTP on port 5000, so terminate TLS in front of it. Its
health check is GET /SASjsApi/info, which needs no authentication.
The same image is packaged for Cloudron as a community app - paste this URL into the dashboard's Community Apps field:
https://raw.githubusercontent.com/sasjs/server/main/container/cloudron/CloudronVersions.json
Cloudron installs ghcr.io/sasjs/server from the app package in
container/cloudron/, wiring up its MongoDB, single
sign-on (OIDC), access control and backups. The entrypoint detects the
platform's addon variables by itself, so one image serves both.
When launching the app, it will make use of specific environment variables. These can be set in the following places:
- Configured globally in
/etc/environmentfile - Export in terminal or shell script (
export VAR=VALUE) - Prepended in the command
- Enter in the
.envfile alongside the executable
Example contents of a .env file:
#
## Core Settings
#
# MODE options: [desktop|server] default: `desktop`
# Desktop mode is single user and designed for workstation use
# Server mode is multi-user and suitable for intranet / internet use
MODE=
# The account the server must run as.
# Unset: the server refuses to start as root.
# Set to a username: the server must be running as that account, or it refuses
# to start - so it is an assertion, not a hint.
# RUN_AS=root is the deliberate override.
# This application executes uploaded code by design, so the identity the server
# runs under is the privilege every connected user gets. In desktop mode there
# is no authentication at all.
# default: unset (root refused)
RUN_AS=
# A comma separated string that defines the available runTimes.
# Priority is given to the runtime that comes first in the string.
# Possible options at the moment are sas, js, py and r
# This string sets the priority of the available analytic runtimes
# Valid runtimes are SAS (sas), JavaScript (js), Python (py) and R (r)
# For each option provided, there should be a corresponding path,
# eg SAS_PATH, NODE_PATH, PYTHON_PATH or RSCRIPT_PATH
# Priority is given to runtimes earlier in the string
# Example options: [sas,js,py | js,py | sas | sas,js | r | sas,r]
RUN_TIMES=
# Path to SAS executable (sas.exe / sas.sh)
SAS_PATH=/path/to/sas/executable.exe
# Path to Node.js executable
NODE_PATH=~/.nvm/versions/node/v16.14.0/bin/node
# Path to Python executable
PYTHON_PATH=/usr/bin/python
# Path to R executable
R_PATH=/usr/bin/Rscript
# Path to working directory
# This location is for SAS WORK, staged files, DRIVE, configuration etc
SASJS_ROOT=./sasjs_root
# This location is for files, sasjs packages and appStreamConfig.json
DRIVE_LOCATION=./sasjs_root/drive
# options: [http|https] default: http
PROTOCOL=
# default: 5000
PORT=
# options: [sas9|sasviya]
# If not present, mocking function is disabled
MOCK_SERVERTYPE=
# default: /api/mocks
# Path to mocking folder, for generic responses, it's sub directories should be: sas9, viya, sasjs
# Server will automatically use subdirectory accordingly
STATIC_MOCK_LOCATION=
#
## Additional SAS Options
#
# On windows use SAS_OPTIONS and on unix use SASV9_OPTIONS
# Any options set here are automatically applied in the SAS session
# See: https://documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/hostunx/p0wrdmqp8k0oyyn1xbx3bp3qy2wl.htm
# And: https://documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/hostwin/p0drw76qo0gig2n1kcoliekh605k.htm#p09y7hx0grw1gin1giuvrjyx61m6
SAS_OPTIONS= -NOXCMD
SASV9_OPTIONS= -NOXCMD
#
## Additional Web Server Options
#
# ENV variables for PROTOCOL: `https`
PRIVATE_KEY=privkey.pem (required)
CERT_CHAIN=certificate.pem (required)
CA_ROOT=fullchain.pem (optional)
## ENV variables required for MODE: `server`
DB_CONNECT=mongodb+srv://<DB_USERNAME>:<DB_PASSWORD>@<CLUSTER>/<DB_NAME>?retryWrites=true&w=majority
# options: [mongodb|cosmos_mongodb] default: mongodb
DB_TYPE=
# AUTH_PROVIDERS options: [ldap|oidc] default: ``
# More than one provider may be listed, space or comma separated
AUTH_PROVIDERS=
## ENV variables required for AUTH_MECHANISM: `ldap`
LDAP_URL= <LDAP_SERVER_URL>
LDAP_BIND_DN= <cn=admin,ou=system,dc=cloudron>
LDAP_BIND_PASSWORD = <password>
LDAP_USERS_BASE_DN = <ou=users,dc=cloudron>
LDAP_GROUPS_BASE_DN = <ou=groups,dc=cloudron>
## ENV variables required for AUTH_PROVIDERS: `oidc`
# Sign-in uses PKCE (RFC 7636) with the S256 challenge method. No variable
# controls it, and the provider needs no extra registration for it.
# Sign-in also requires group membership, by fixed name: the provider's
# `groups` claim must contain `sasjs-users` (ordinary user) or `sasjs-admins`
# (administrator), and a user in neither is refused. The names are not
# configurable. The `groups` scope is requested automatically from providers
# that advertise it, and providers that cannot express groups are exempt.
# Each sign-in also mirrors those memberships onto local groups of the same
# name, so a Permission can be granted to a provider group rather than to each
# of its members in turn. The mirror is authoritative: a membership the
# provider stops asserting is removed at the next sign-in. A group whose name
# an administrator has already used locally, or that another provider owns, is
# left alone and reported in the log rather than adopted.
# The issuer URL of your provider. Endpoints are read from
# <OIDC_ISSUER_URL>/.well-known/openid-configuration
# Supply OIDC_DISCOVERY_URL instead if discovery is served elsewhere.
# One of the two is required.
OIDC_ISSUER_URL=
OIDC_DISCOVERY_URL=
# Required
OIDC_CLIENT_ID=
OIDC_CLIENT_SECRET=
# The callback URL, registered with the provider and matching exactly.
# The path is fixed by the server.
# Required
OIDC_REDIRECT_URI=
# The label shown on the sign-in button, as in "Sign in with <name>"
# default: OpenID Connect
OIDC_PROVIDER_NAME=
# Scopes requested from the provider - must include `openid`
# default: openid profile email
OIDC_SCOPE=
# Claim used to derive the SASjs username for a new user, normalised to
# lowercase alphanumerics, max 16 characters
# default: preferred_username (falling back to `sub` when absent)
OIDC_USERNAME_CLAIM=
# Algorithm used to verify the provider's id_token signature - set it to match
# what the provider signs with
# options: [RS256|RS384|RS512|ES256|ES384|ES512|EdDSA] default: RS256
OIDC_SIGNING_ALG=
# Whether a successful sign-in with no SASjs account creates one.
# The first user provisioned becomes an admin; later ones do not.
# options: [true|false] default: true
OIDC_JIT_PROVISION=
# Where the provider sends the browser after the single sign-on session closes.
# Optional - logout returns to the home page when omitted.
OIDC_POST_LOGOUT_REDIRECT_URI=
# options: [disable|enable] default: `disable` for `server` & `enable` for `desktop`
# If enabled, be sure to also configure the WHITELIST of third party servers.
CORS=
# options: <http://localhost:3000 https://abc.com ...> space separated urls
WHITELIST=
# HELMET Cross Origin Embedder Policy
# Sets the Cross-Origin-Embedder-Policy header to require-corp when `true`
# options: [true|false] default: true
# Docs: https://helmetjs.github.io/#reference (`crossOriginEmbedderPolicy`)
HELMET_COEP=
# HELMET Content Security Policy
# Path to a json file containing HELMET `contentSecurityPolicy` directives
# Docs: https://helmetjs.github.io/#reference
#
# The default policy allows no inline scripts and no inline event handlers, so
# a page with an injected <script> tag cannot run it. An application deployed
# on the server that needs inline scripts (many Angular and Data Controller
# builds inject them) loosens the policy with its own config file.
#
# Example config:
# {
# "img-src": ["'self'", "data:"],
# "script-src": ["'self'"],
# "script-src-attr": ["'none'"]
# }
#
# Loosened config for an app that requires inline scripts:
# {
# "img-src": ["'self'", "data:"],
# "script-src": ["'self'", "'unsafe-inline'"],
# "script-src-attr": ["'self'", "'unsafe-inline'"]
# }
HELMET_CSP_CONFIG_PATH=./csp.config.json
# Failed password attempts on the login route are throttled by username.
# Only valid for MODE: server.
#
# The lockout is keyed on the username rather than the IP address: behind a
# reverse proxy every client shares the proxy's address, so an IP-keyed limit
# locks out the whole deployment rather than an attacker.
# Failed attempts against one username before further attempts are refused
# with `429 Too Many Failed Attempts`
# default: 5
MAX_LOGIN_FAILURES=5
# How long the username stays locked out once that threshold is reached
# default: 15
LOGIN_LOCKOUT_MINUTES=15
# Password sign-in for local (database) accounts.
# Set to false to refuse it outright: the stored password is never compared, so
# a local account cannot sign in at all. Accounts that authenticate through a
# provider (eg OIDC) or through LDAP are unaffected. Use it on a deployment
# whose accounts all live in the provider, where the local password is the only
# credential an unauthenticated caller can attack.
# The login screen hides the password form while this is false, so a deployment
# that signs everyone in through a provider shows only the provider's button.
# Cannot be false when AUTH_PROVIDERS is empty, since then no account could
# sign in.
# options: [true|false] default: true
LOCAL_LOGIN_ENABLED=true
# Name of the local admin user, created on startup only when
# ADMIN_PASSWORD_INITIAL is set
# default: `secretuser`
ADMIN_USERNAME=secretuser
# Password for the ADMIN_USERNAME, which is in place until the first login
# There is no default: a default would ship a publicly-known credential.
# In server mode it is required unless an external auth provider is enabled -
# with a provider, leaving it unset seeds no local admin at all and the first
# user to sign in through the provider becomes the administrator.
ADMIN_PASSWORD_INITIAL=
# Specify whether app has to reset the ADMIN_USERNAME's password or not
# Default is NO. Possible options are YES and NO
# If ADMIN_PASSWORD_RESET is YES then the ADMIN_USERNAME will be prompted to change the password from ADMIN_PASSWORD_INITIAL on their next login. This will repeat on every server restart, unless the option is removed / set to NO.
ADMIN_PASSWORD_RESET=NO
# LOG_FORMAT_MORGAN options: [combined|common|dev|short|tiny] default: `common`
# Docs: https://www.npmjs.com/package/morgan#predefined-formats
LOG_FORMAT_MORGAN=
# This location is for server logs with classical UNIX logrotate behavior
LOG_LOCATION=./sasjs_root/logs
Normally the server process will stop when your terminal dies. To keep it going you can use the following suggested approaches:
- Linux Background Job
- NPM package
pm2
Trigger the command using NOHUP, redirecting the output commands, eg nohup ./api-linux > server.log 2>&1 &.
You can now see the job running using the jobs command. To ensure that it will still run when your terminal is closed, execute the disown command. To kill it later, use the kill -9 <pid> command. You can see your sessions using top -u <userid>. Type c to see the commands being run against each pid.
Install the npm package pm2 (npm install pm2@latest -g) and execute, eg as follows:
export SAS_PATH=/opt/sas9/SASHome/SASFoundation/9.4/sasexe/sas
export PORT=5001
export SASJS_ROOT=./sasjs_root
pm2 start api-linuxTo get the logs (and some useful commands):
pm2 [list|ls|status]
pm2 logs
pm2 logs --lines 200Managing processes:
pm2 restart app_name
pm2 reload app_name
pm2 stop app_name
pm2 delete app_name
Instead of app_name you can pass:
allto act on all processesidto act on a specific process id
The following credentials can be used for the initial connection to SASjs/server. It is highly recommended to change these on first use.
- CLIENTID:
clientID1 - USERNAME:
secretuser - PASSWORD:
secretpassword
Thanks goes to these wonderful people (emoji key):
Saad Jutt 💻 |
Sabir Hassan 💻 |
Yury Shkoda 💻 |
Mihajlo Medjedovic 💻 |
Allan Bowe 💻 📖 |
Vladislav Parhomchik |
Koen Knapen 📓 |
This project follows the all-contributors specification. Contributions of any kind welcome!