xyOps Workflow Automation on Ubuntu 24.04 on Azure User Guide
Overview
xyOps is an open source workflow automation and server monitoring platform. You define events that say what to run, where to run it and when; the scheduler fires them on a timetable, on an interval, once, or on demand; and every launch becomes a job with a live streaming log, a progress meter, captured output and full history. Jobs chain into other jobs or into visual workflows with branching, fan out and fan in, while the same platform collects CPU, memory, disk and network metrics from every registered server and raises alerts and tickets when a threshold is crossed.
This cloudimg image ships xyOps 1.0.88 as a complete single server appliance. The conductor (web console, scheduler, API and storage) runs as the dedicated unprivileged xyops system user on Node.js 22 LTS, and the bundled xySat satellite job runner is started by the conductor in the same VM, so this one machine can actually execute work with no second node to provision. The conductor is bound to 127.0.0.1:5522 and nginx on port 80 reverse proxies to it, including the WebSockets that carry live job logs and server metrics. A unique admin password and a unique session signing secret are generated on the first boot of every VM, and first boot proves the upstream default admin/admin login is rejected before it ever opens the public port. Backed by 24/7 cloudimg support.
What is included:
- xyOps 1.0.88 (pinned upstream release, BSD-3-Clause) running as the
xyopssystem service on Node.js 22 LTS - The bundled xySat 1.0.39 job runner (also BSD-3-Clause), started automatically by the conductor, so a single VM can run jobs out of the box
- The stock embedded storage engine (SQLite document store plus a filesystem binary store) at
/opt/xyops/data, so there is no external database to run - xyOps bound to
127.0.0.1:5522, fronted by nginx on port 80 including the live-log WebSocket - Port 80 serving an unauthenticated
/healthzendpoint for load balancer probes, with every API route requiring authentication - A unique
adminpassword generated on first boot and written to a root only file, so no shared or default admin ships in the image - A fresh per VM session and satellite signing secret generated on first boot, never a shared image baked secret
- Stock plugins out of the box: Shell Script, HTTP Request, Docker Run, Fire Web Hook and a Test Plugin
/usr/local/sbin/xyops-selftest.sh, a self test that proves your instance can still create and run a job end to end- A production dependency licence audit generated at build time at
/opt/xyops/CLOUDIMG-DEPENDENCY-LICENCES.txt - 24/7 cloudimg support
Security note before you start. xyOps runs shell commands on your servers on your behalf. That is what it is for, and it means an exposed, weakly protected instance is remote code execution. Keep this VM behind a restricted network security group or a VPN, never open port 80 to the whole internet, and put a TLS terminating proxy in front of it before production use. Step 3 and the Maintenance section below show how.
xyOps is developed by PixlCore LLC. cloudimg is not affiliated with, endorsed by or sponsored by PixlCore LLC; the product name is used only to identify the unmodified open source software packaged in this image. xyOps is the successor to the same author's earlier Cronicle scheduler.
Prerequisites
An active Azure subscription, an SSH key pair, and a VNet plus subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is a reasonable starting point; size up if you plan to run many concurrent jobs or register a large fleet of servers. NSG inbound: allow 22/tcp from your management network and 80/tcp from your management network only (and 443/tcp if you add TLS). The web console is served over plain HTTP by default, so for production put your own domain and a trusted certificate in front of it (see Maintenance).
Step 1 - Deploy from the Azure Marketplace
Sign in to the Azure Portal, choose Create a resource, search the Marketplace for xyOps by cloudimg, and select Create. On Basics pick your subscription, resource group, region and size; under Administrator account choose SSH public key and paste your key; under Inbound port rules allow SSH (22) only, then add a scoped HTTP rule after deployment as shown in Step 3. Review and create.
Step 2 - Deploy from the Azure CLI
az vm create \
--resource-group <your-rg> \
--name xyops \
--image <marketplace-image-urn> \
--size Standard_B2s \
--admin-username azureuser \
--ssh-key-values ~/.ssh/id_ed25519.pub \
--vnet-name <your-vnet> --subnet <your-subnet> \
--public-ip-sku Standard
Step 3 - Restrict who can reach the console, then connect
Because xyOps executes commands for you, open port 80 to your own network rather than to the internet. Replace <your-office-cidr> with the address range you will administer from:
az network nsg rule create \
--resource-group <your-rg> \
--nsg-name <your-nsg> \
--name allow-xyops-web \
--priority 1010 \
--source-address-prefixes <your-office-cidr> \
--destination-port-ranges 80 \
--access Allow --protocol Tcp --direction Inbound
Then connect over SSH:
ssh azureuser@<vm-public-ip>
Step 4 - Confirm the services are running
systemctl is-active xyops.service nginx.service
ss -tlnp | grep -E ':5522|:80 '
/opt/xyops/bin/control.sh version
Both services report active, and the version prints 1.0.88. Note where each process listens: xyOps itself is bound to 127.0.0.1:5522 and is never exposed directly on a public interface, while nginx listens on port 80 and reverse proxies to it.

Step 5 - Retrieve your admin password
The xyOps admin password is generated uniquely on the first boot of your VM and written to a root only file:
sudo stat -c '%a %U:%G %n' /root/xyops-credentials.txt
sudo cat /root/xyops-credentials.txt
The file contains XYOPS_URL, XYOPS_USERNAME (admin) and XYOPS_PASSWORD. It is mode 0600 owned by root, so only an administrator can read it. Store the password somewhere safe.
The upstream project ships a documented default admin/admin login. It does not exist on your VM: first boot mints a 28 character password, proves the default is rejected, and only then opens port 80. You can confirm that yourself:
curl -s -H 'Content-Type: application/json' \
-d '{"username":"admin","password":"admin"}' \
http://localhost/api/user/login
The response is {"code":"login","description":"Username or password incorrect."}. xyOps always answers this endpoint with HTTP 200 and reports the outcome in the JSON body's code field, so read the body rather than the status code.

Step 6 - Confirm the health endpoint and prove a real job runs
nginx serves an unauthenticated health endpoint for load balancers and probes, and the image ships a self test that does not just check that a page loads: it signs in with your per VM password, confirms the default and a wrong password are both rejected, confirms the local job runner is registered, then creates an event, runs it, and checks the captured output.
curl -s -o /dev/null -w 'GET /healthz HTTP %{http_code}\n' http://localhost/healthz
curl -s -o /dev/null -w 'GET / HTTP %{http_code}\n' http://localhost/
sudo /usr/local/sbin/xyops-selftest.sh
Both requests return HTTP 200, and the self test prints a line beginning OK with the job id it ran, how long it took and how many bytes of output it captured. It cleans up the event it created, and it repairs xyOps' failed login counters afterwards so it is safe to run as often as you like.

Step 7 - Sign in to the web console
Browse to http://<vm-public-ip>/. You land on the xyOps User Login page. Enter admin as the username and the password from Step 5, then select Login.

Step 8 - Explore the dashboard
After signing in you land on the Dashboard. It shows the size of your cluster (conductors and servers), current alerts, active jobs, today's job counts and success rate, and resource usage. A fresh appliance shows 1 conductor and 1 server: the conductor is this VM, and the server is the bundled xySat job runner that lets a single VM execute work.

Step 9 - Create a scheduled event
Select Events in the sidebar, then New Event.... Give the event a title, choose a Category (the image ships a ready made General category), a Target (the Main Group covers this VM's own runner) and a Plugin - the image ships Shell Script, HTTP Request, Docker Run, Fire Web Hook and a Test Plugin. Paste your script, add a Trigger (a schedule, an interval, a single shot or a manual run), then save.
The screenshot below shows two real events in the list: a Nightly log rotation event on a 03:30 daily schedule, and an Hourly disk-space check on the hour. The Status column shows the result of each event's most recent job, and the Run, Edit and History actions are on every row.

Step 10 - Run a job and read its captured output
Select Run next to an event to launch it immediately. While it runs, xyOps streams progress, CPU and memory usage and the live log over a WebSocket; when it finishes, the job moves to All Completed with its full record retained.
Open the job to see everything xyOps captured: the success banner, the job summary (which event, which plugin, which server, when it started and finished, how long it took, average CPU and memory) and the Job Output block holding exactly what your script printed.

You can do the same thing from the API, which is useful for wiring xyOps into your own tooling. Substitute the value of XYOPS_PASSWORD from Step 5 for <XYOPS_PASSWORD>; because the command embeds your unique password, run it interactively rather than from a script. Note that authenticated POST calls need both the session id from the login cookie and the csrf_token from the login body:
LOGIN=$(mktemp) ; HDR=$(mktemp)
curl -s -D "$HDR" -o "$LOGIN" -H 'Content-Type: application/json' \
-d '{"username":"admin","password":"<XYOPS_PASSWORD>"}' \
http://localhost/api/user/login
SID=$(grep -i '^set-cookie' "$HDR" | sed -E 's/.*session_id=([^;]+).*/\1/' | tr -d '\r' | head -1)
CSRF=$(python3 -c 'import json,sys
try: print(json.load(open(sys.argv[1])).get("csrf_token",""))
except Exception: print("")' "$LOGIN")
curl -s -H 'Content-Type: application/json' -H "X-Session-ID: $SID" -H "X-CSRF-Token: $CSRF" \
"http://localhost/api/app/get_active_servers/v1" \
| python3 -c 'import json,sys
try: d=json.loads(sys.stdin.read() or "{}")
except Exception: d={}
print("registered job runners:", len(d.get("rows") or []))'
rm -f "$LOGIN" "$HDR"
This prints registered job runners: 1 on a single VM appliance. The same session works for create_event, run_event and the rest of the REST API; see the xyOps API reference for the full list.
Step 11 - Confirm the licences shipped with your image
Every image carries its upstream licences and a production dependency licence audit generated when the image was built:
head -3 /opt/xyops/LICENSE.md
head -5 /opt/xyops/CLOUDIMG-DEPENDENCY-LICENCES.txt
xyOps and the bundled xySat runner are both BSD-3-Clause. The audit lists every package in the production dependency tree with its licence, resolving the licence from the package's own LICENSE file where its package.json omits the field.

Maintenance
- Where your data lives: the embedded storage (events, workflows, jobs and their output, users, servers, alerts and tickets) is under
/opt/xyops/data, logs are under/opt/xyops/logs, and configuration is/opt/xyops/conf/config.json. Check size withdu -sh /opt/xyops/data. - Backups: back up the storage directory while the service is stopped, to a root only location. Do not copy
/opt/xyops/conf/or/root/xyops-credentials.txtinto a backup archive you move off box without protecting them: the config holds this VM's session signing secret and the credentials file holds your admin password.bash sudo systemctl stop xyops.service sudo install -d -o root -g root -m 0700 /var/backups/xyops sudo tar czf /var/backups/xyops/xyops-data-$(date +%F).tar.gz -C /opt/xyops data sudo chmod 0600 /var/backups/xyops/xyops-data-*.tar.gz sudo systemctl start xyops.service - Rotate the admin password: stop the service, write a new one, and start it again.
bash sudo systemctl stop xyops.service sudo runuser -u xyops -- /usr/bin/node /opt/xyops/bin/storage-cli.js admin admin '<new-password>' sudo systemctl start xyops.serviceUserunuserrather thansudo -uso the new password is not written into the systemd journal. - Locked out after too many bad logins? xyOps locks an account after five failed logins in an hour. Rotating the password with the command above clears the lock.
- Serve a real domain and TLS: point a DNS name at the VM, obtain a trusted certificate (for example with certbot), and front xyOps with nginx TLS on port 443. Then set
base_app_urlin/opt/xyops/conf/config.jsonto yourhttps://URL so the links xyOps generates in emails, tickets and web hooks are correct, and restart the service. - Add worker servers: this appliance runs jobs on itself out of the box. To spread work across more machines, open Servers, select Add Server..., and run the generated one line installer on the target host; it installs the xySat agent and joins it to this conductor. Make sure the workers can reach this VM by hostname.
- Users and access control: add teammates and set per privilege access from User Accounts and User Roles under ADMIN. The
adminprivilege grants everything; prefer a role with only the privileges each person needs. - Upgrades:
sudo -u xyops /opt/xyops/bin/control.sh upgradeupgrades in place and preserves your data, configuration and users. Take a backup first. - Security patches: unattended-upgrades remains enabled so the OS continues to receive security updates automatically.
Support
cloudimg provides 24/7 expert support for this image. Contact support@cloudimg.co.uk.