alpacon
Server Management

Server Management

Listing servers

Request:

GET /api/servers/servers/

Starred servers can be listed using boolean filter starred.

GET /api/servers/servers/?starred=true

Adding a server

Request:

POST /api/servers/servers/
{
    "name": "test",
    "platform": "debian" or "rhel",
}

Platform should be either debian or rhel.

Response:

HTTP 201 Created
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
{
    "name": "testing",
    "id": "821b93bd-6913-4416-bb21-0e04f0a8b267",
    "instruction_1": "curl http://host.docker.internal:8000/api/servers/installers/4052825a-16cb-4226-a486-881601f87561/ | sudo bash",
    "instruction_2": "#!/bin/bash\n\napt-get update && apt-get -y install python3 python3-pip\n\ncurl -sSLf -o /tmp/alpamon-1.0.0-py3-none-any.whl http://host.docker.internal:8000/api/packages/python/entries/f6c9bc4b-83b6-498c-9a06-11fbd9be7309/download/\npip3 install -U /tmp/alpamon-1.0.0-py3-none-any.whl\nrm -f /tmp/alpamon-1.0.0-py3-none-any.whl\n\nexport ALPACON_URL=\"http://host.docker.internal:8000\"\nexport ALPAMON_ID=\"821b93bd-6913-4416-bb21-0e04f0a8b267\"\nexport ALPAMON_KEY=\"z7k5gYDPf7fUVekS5TEP6JqryWAFdyrf\"\n\nalpamon-deploy install\n"
}

Clients may use two installation instructions. As they contains the server's id and a secret key, clients should handle them safely.

Retrieving a specific server

GET /api/servers/servers/<id>/

An example response data looks like the followings.

Request:

GET /api/servers/servers/7a50ea6c-2138-4d3f-9633-e50694c847c4/

Response:

HTTP 200 OK
Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
{
    "id": "7a50ea6c-2138-4d3f-9633-e50694c847c4",
    "name": "test",
    "remote_ip": "127.0.0.1",
    "status": {
        "code": "ok",
        "icon": "circle-check",
        "meta": {
            "delay_1d": 127.24366,
            "delay_1h": 0.902331,
            "delay_1w": 122.499982,
            "delay_now": 0.804537
        },
        "text": "Good",
        "color": "success",
        "messages": [
            "Server is okay."
        ]
    },
    "is_connected": true,
    "commissioned": true,
    "starred": false,
    "version": "1.0.0",
    "osquery_version": "5.1.0",
    "cpu_physical_cores": 4,
    "cpu_logical_cores": 8,
    "cpu_type": "x86_64h",
    "physical_memory": 17179869184,
    "os_name": "macOS",
    "os_version": "12.6",
    "uptime": 351540.592445,
    "boot_time": "2022-09-27T13:17:07Z",
    "last_connectivity": "2022-09-30T16:20:13.343055Z",
    "started_at": "2022-09-27T23:36:27.634597+09:00",
    "added_at": "2022-09-22T23:26:17.303065+09:00",
    "updated_at": "2022-10-01T01:20:16.566762+09:00",
    "user": "7bbcbaf3-aae3-4b6d-b2f2-ba374a878abb"
}
  • id: Server id
  • name: Server name
  • remote_ip: The IP address detected by alpacon
  • status: Detailed server status in JSON format including:
    • code: ok if status is good, warn or error if something is wrong.
    • text: A description of the status code. Can be ignored if the client uses custom descriptions.
    • icon, color: Icon and color to show the status. Can be ignored if the client uses custom icons. This fields can be used like <i class="text-{{ object.status.color }} fa-solid fa-{{ object.status.icon }}"></i>
    • messages: Detailed messages describing the server status. Errors, if any, are included here.
    • meta: Use delay_now in meta to display current delay status of the server.
  • is_connected: Whether alpamon is currently connected
  • commissioned: Whether alpamon has reported the system status
  • starred: Whether alpamon is starred
  • version: Version of alpamon
  • osquery_version: Version of osquery (osquery is used by alpamon to collect system information)
  • uptime: Time elapsed since the last system boot
  • boot_time: Timestamp when the system last booted
  • last_connectivity: Timestamp when alpamon was last contacted
  • started_at: Timestamp when alpamon was started
  • added_at: Timestamp when alpamon was added
  • updated_at: Timestamp when alpamon was updated
  • user: The user who added the server

Following URLs provide more detailed information about a server.

  • /api/servers/servers/<id>/info/: System hardware information
  • /api/servers/servers/<id>/os/: Operating system information
  • /api/servers/servers/<id>/time/: Timezone and uptime
  • /api/servers/servers/<id>/users/: System users
  • /api/servers/servers/<id>/groups/: System groups
  • /api/servers/servers/<id>/interfaces/: Network interfaces
  • /api/servers/servers/<id>/packages/: System packages

You can list system packages using the following API. This API supports detailed query arguments and pagination.

Request:

GET /api/proc/packages/?server=7a50ea6c-2138-4d3f-9633-e50694c847c4&search=postgresql

Supported query arguments: search, server, name, arch

Response:

HTTP 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
{
    "count": 1,
    "next": null,
    "previous": null,
    "results": [
        {
            "id": "48369fcc-742d-4ed3-8740-0fa1960c0d17",
            "added_at": "2022-10-14T18:55:44.198382+09:00",
            "name": "postgresql@14",
            "version": "14.5_5",
            "source": "/opt/homebrew/Cellar/postgresql@14/",
            "arch": null
        }
    ]
}

Starring a server

Request:

POST /api/servers/servers/7a50ea6c-2138-4d3f-9633-e50694c847c4/star/
{
    "status": true
}

If you want to star a server, set status to true. Otherwise, set it false.

Please note that up to 5 servers can be starred by a user.

Updating or deleting a server

  • URL: /api/servers/servers/<id>/
  • HTTP method: PUT, PATCH, DELETE
  • Request data: name, key, enabled, (alpamon only: version, osquery_version)

Getting or updating server itself

[alpamon only] An authenticated server can access the above API by setting the id field to -.

  • URL: /api/servers/servers/-/
  • HTTP method: GET, PUT

Commit information

[alpamon only] Servers may commit gathered information using the following API.

  • URL: /api/servers/servers/-/commit/

Websh: A web-based terminal

Websh is a new protocol for remotely accessing servers. Clients can use the following API to create and manage Websh sessions.

New terminal

You can obtain a Websh session using the following API.

  • URL: /api/websh/sessions/<uuid:id>/
  • HTTP method: POST
  • Request data: rows, cols (Both are positive numbers indicating the terminal size on the client.)
  • Response data
    • websocket: A one-time WebSocket URL for accessing the terminal. The actual URL would look like wss://alpacon.io/ws/websh/<session_id>/<token>/.
    • api: An API URL for session object that can be used to adjust terminal size dynamically. Clients can call this API when a user resizes the terminal window.
  • Status codes: 200 OK on success.

Session adjustment

You can adjust the size of a Websh session. Please note that session resizing takes a round-trip delay to alpamon.

  • URL: /api/websh/sessions/<uuid:id>/
  • HTTP methods: PUT, PATCH
  • Request data: rows, cols
  • Response data: rows, cols
  • Status codes: 200 OK on success.

Implementation example

Here is an example implementing this feature at Javascript front. Following example is written based on xterm 3.x. The latest xterm is 4.x.

<script src="{% static 'xterm/xterm.js' %}"></script>
<script src="{% static 'xterm/addons/attach/attach.js' %}"></script>
<script src="{% static 'xterm/addons/fit/fit.js' %}"></script>
<script src="{% static 'xterm/addons/webLinks/webLinks.js' %}"></script>
<script>
  var resizeDelay = 500;
  var timer = null;
 
  Terminal.applyAddon(attach);
  Terminal.applyAddon(fit);
  Terminal.applyAddon(webLinks);
 
  var term = new Terminal({
    fontFamily: '"Monaco", monospace',
    fontSize: 14,
    scrollback: 100000,
  });
  term.open(document.getElementById('terminal'));
  term.fit();
  term.webLinksInit();
  term.focus();
 
  // obtain a new websh session
  $.post("{% url 'api:servers:server-websh' object.pk %}", {
    rows: term.rows,
    cols: term.cols,
  }, function(data) {
    // data contains url for websocket and api endpoints
    // make a websocket connection and register a resize event handler
 
    var api_url = data.api;
    var socket = new WebSocket(data.websocket);
    socket.onclose = function onClose(event) {
      term.writeln('\r\n\r\n(offline)');
    };
    term.attach(socket);
 
    // resize terminal via api endpoint. delay applied on resize event.
    window.addEventListener('resize', function() {
      clearTimeout(timer);
      timer = setTimeout(function() {
        if (typeof api_url !== 'undefined') {
          term.fit();
          $.ajax({
            url: api_url,
            method: 'patch',
            data: {
              rows: term.rows,
              cols: term.cols,
            }
          });
        }
      }, resizeDelay);
    });
  });
 
  // alert when user is about to leave the screen
  window.addEventListener('beforeunload', function(e) {
    var message = 'You are about to leave this page. Please make sure you really want to leave.';
    (e || window.event).returnValue = message;
    return message;
  });
</script>

Web-based FTP

Websh implements web-based FTP service to support file transfers between users and servers.

Uploading files

You can post a file to /api/websh/uploads/ to upload a file to a server.

  • URL: /api/websh/uploads/
  • HTTP method: POST
  • Request data
    • content: The file content to upload. Use a multipart form, as JSON does not support binary files.
    • path: The destination path for the file. If omitted, the user's home directory will be used by default.
    • server: Server instance id (Must be a UUID, not a server name). You should have valid permission on the server.
  • Response data: A file upload instance. Currently it does not return any meaningful content.
  • Status codes: 201 CREATED on success.

Once the POST request completes successfully, alpacon will transfer the file to alpamon. We are planning to add API support for reporting the transfer status.

Cancelling transfer

(🚧 Work in Progress) You can cancel a transfer request before it reaches the target server.

  • URL: /api/websh/uploads/<uuid:id>/
  • HTTP method: DELETE

Controlling servers

Running a pre-defined command

We provide a set of pre-defined commands which are frequently used when managing servers.

Request:

POST /api/servers/servers/7a50ea6c-2138-4d3f-9633-e50694c847c4/actions/
{
    "action": "update_information"
}

You need to include action in the request data. Supported actions are available in the browsable API.

Response:

HTTP 201 CREATED
Allow: POST, OPTIONS
Content-Type: application/json
Vary: Accept
{
    "id": "2e458d4c-f85a-4a83-badf-90d62991cc12",
    ...
}

Once the request is successfully completed, alpacon will execute it via alpamon. A corresponding command instance will be returned. You can check the result later using: GET /api/events/commands/<id>/.

Running an arbitrary command

You can run an arbitrary command on the system where alpamon is installed. These commands will be executed with your account privileges.

Request:

POST /api/events/commands/
{
    "shell": "system",
    "line": "pwd",
    "data": "",
    "scheduled_at": null,
    "server": "7a50ea6c-2138-4d3f-9633-e50694c847c4"
}

As shown in the example, shell, line, and server are required. data and scheduled_at fields are optional.

  • shell: system for system shell commands, osquery for osquery commands, internal for alpamon's internal commands.
  • line: Arbitrary command line that you want to run
  • data: Optional data for the command. Only used with internal commands.
  • scheduled_at: If specified, alpacon will run the command at the scheduled time. Otherwise, it will be run now.
  • server: The instance ID of the server you want to run this command on.

Response:

HTTP 201 Created
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
{
    "id": "83bf3f02-f70e-49a3-bf47-a39a8d06a9df",
    "shell": "system",
    "line": "pwd",
    "data": "",
    "success": null,
    "result": null,
    "status": {
        "text": "Sent",
        "color": "warning",
        "cancellable": true,
        "message": "Sent command to server, waiting response."
    },
    "response_delay": null,
    "elapsed_time": null,
    "added_at": "2022-10-10T12:02:28.532238+09:00",
    "scheduled_at": "2022-10-10T12:02:28.528700+09:00",
    "delivered_at": "2022-10-10T12:02:28.571022+09:00",
    "acked_at": null,
    "handled_at": null,
    "server": "7a50ea6c-2138-4d3f-9633-e50694c847c4",
    "requested_by": "a540bf0f-8b37-4f03-8546-dd71c6b03329",
    "run_after": []
}

For the information about the response fields, please refer to the next section in this document.

Fetching list of commands

  • URL: /api/events/commands/
  • HTTP method: GET

Request:

GET /api/events/commands/?server=7a50ea6c-2138-4d3f-9633-e50694c847c4&requested_by=

Supported filters are server and requested_by. search keywords are accepted at well.

We use page number pagination. Add page number as a request argument. Examples are ?page=1, page=2, and page=last. We also accept page_size argument no more than 100 for arbitrary page sizing. For more about pagination, refer to this document (opens in a new tab).

Response:

HTTP 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
{
    "count": 110,
    "next": "http://localhost:8000/api/events/commands/?page=2&requested_by=&server=7a50ea6c-2138-4d3f-9633-e50694c847c4",
    "previous": null,
    "results": [
        {
        {
            "id": "8befa6c4-9620-4aea-9fde-cac1335e01f8",
            "shell": "internal",
            "line": "ping",
            "data": null,
            "success": true,
            "result": "2022-10-10T11:45:32.109641+09:00",
            "status": {
                "text": "Success",
                "color": "success",
                "cancellable": false,
                "message": "Finished at 2022-10-10 11:45:32.244418+09:00."
            },
            "response_delay": 0.132617,
            "elapsed_time": 0.067553,
            "added_at": "2022-10-10T11:45:32.044390+09:00",
            "scheduled_at": "2022-10-10T11:45:32.044248+09:00",
            "delivered_at": "2022-10-10T11:45:32.044248+09:00",
            "acked_at": "2022-10-10T11:45:32.176865+09:00",
            "handled_at": "2022-10-10T11:45:32.244418+09:00",
            "server": "7a50ea6c-2138-4d3f-9633-e50694c847c4",
            "requested_by": null,
            "run_after": []
        },
        {
            "id": "869a1600-681e-409a-acd3-a63d621dce22",
            "shell": "system",
            "line": "pwd",
            "data": "",
            "success": true,
            "result": "/Users/eunyoung/projects/alpacax/alpamon",
            "status": {
                "text": "Success",
                "color": "success",
                "cancellable": false,
                "message": "Finished at 2022-10-10 11:48:00.628272+09:00."
            },
            "response_delay": 0.370961,
            "elapsed_time": 0.072433,
            "added_at": "2022-10-09T22:12:00.077467+09:00",
            "scheduled_at": "2022-10-09T23:11:00+09:00",
            "delivered_at": "2022-10-10T11:48:00.184878+09:00",
            "acked_at": "2022-10-10T11:48:00.555839+09:00",
            "handled_at": "2022-10-10T11:48:00.628272+09:00",
            "server": "7a50ea6c-2138-4d3f-9633-e50694c847c4",
            "requested_by": "a540bf0f-8b37-4f03-8546-dd71c6b03329",
            "run_after": []
        },
        ...
]
  • success: true if the command succeeded, false otherwise. We use exitcode to determine this for system commands. This field will be null until the command is executed.
  • result: The output result of the command. This field will be null until the command is executed.
  • status: A dictionary containing detailed information about the command's current status.
    • text: A short description about the command. Possible values are Scheduled, Queued, Sent, Acked, Success, Failed, Stuck, and Error.
    • color: Which text color to use. You can prefix this color with text- or bg- if you are using Bootstrap.
    • cancellable: Indicates whether the commands can be cancelled. A command can only be cancelled if it has not yet been sent to the server.
    • message: A detailed message describing the command status
  • response_delay: The time taken to deliver this command. If the command has not been acknowledged by the server, this field will be null.
  • elapsed_time: The time taken to execute the command. If the command has not run, this field will be null.
  • scheduled_at: The scheduled time for this command. If not provided, the command will executed immediately.
  • delivered_at: Timestamp when Alpacon sent the command to alpamon.
  • acked_at: Timestamp when alpamon acknowledged the command. The response delay means acked_at - delivered_at.
  • handled_at: Timestamp that alpamon finished executing the command. The elapsed time means handled_at - acked_at.
  • server: The id of the target server.
  • requested_by: The id of the operator who requested the command.
  • run_after: A list of commands that should be run prior to this command.

Fetching list of logs

  • URL: /api/history/logs/
  • HTTP method: GET

Request:

GET /api/history/logs/?server=7a50ea6c-2138-4d3f-9633-e50694c847c4&program=&level=&name=&pid=&tid=

Supported filters are server and more. search keywords are accepted as well. Page number is accepted.

Response header:

HTTP 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

Response data:

  • date: Log date recorded by the server itself or alpamon
  • added_at: Added time recorded by alpacon. Logs are ordered by this field by default.
  • level: 10 (DEBUG), 20 (INFO), 30 (WARN), 40 (ERROR), 50 (CRITICAL)
  • msg: Log message
{
    "count": 128,
    "next": "http://localhost:8000/api/history/logs/?level=&name=&page=2&pid=&program=&server=7a50ea6c-2138-4d3f-9633-e50694c847c4&tid=",
    "previous": null,
    "results": [
        {
            "id": 128,
            "added_at": "2022-10-02T00:05:14.660008+09:00",
            "date": "2022-10-02T00:05:14.456662+09:00",
            "program": "alpamon",
            "level": 30,
            "name": "websocket",
            "path": "/Users/eunyoung/projects/alpacax/alpamon/env/lib/python3.9/site-packages/websocket/_logging.py",
            "lineno": 66,
            "pid": 10456,
            "tid": 4393072128,
            "process": "MainProcess",
            "thread": "MainThread",
            "msg": "websocket connected",
            "server": "7a50ea6c-2138-4d3f-9633-e50694c847c4"
        },
        {
            "id": 127,
            "added_at": "2022-10-01T01:20:13.555034+09:00",
            "date": "2022-10-01T01:20:13.347561+09:00",
            "program": "alpamon",
            "level": 30,
            "name": "websocket",
            "path": "/Users/eunyoung/projects/alpacax/alpamon/env/lib/python3.9/site-packages/websocket/_logging.py",
            "lineno": 66,
            "pid": 10456,
            "tid": 4393072128,
            "process": "MainProcess",
            "thread": "MainThread",
            "msg": "websocket connected",
            "server": "7a50ea6c-2138-4d3f-9633-e50694c847c4"
        },
        ...
]

Notes on servers

Users can leave notes on a server to record specific details or information. Notes can be marked as either private or pinned.

Making a note

Use POST to create a new note.

Request:

POST /api/servers/notes/
{
    "server": "a7282bea-31d7-4b55-a43e-97e1240c90ab",
    "content": "hello world.",
    "private": false,
}

The length of content is limited to 512. Markdown is supported.

Response:

{
    "id": "57649aa5-dfca-4041-9b94-d6b61f5842e7",
    "server": "a7282bea-31d7-4b55-a43e-97e1240c90ab",
    "author": "a540bf0f-8b37-4f03-8546-dd71c6b03329",
    "content": "hello world.",
    "private": false,
    "pinned": false,
    "updated_at": "2022-12-04T12:44:43.248506+09:00"
}

Listing notes

You can retrieve a list of notes for a given server.

GET /api/servers/notes/?server=a7282bea-31d7-4b55-a43e-97e1240c90ab
{
    "count": 2,
    "next": null,
    "previous": null,
    "results": [
        {
            "id": "e2fb88fb-00e3-49f0-9256-eb01734c7f09",
            "server": "a7282bea-31d7-4b55-a43e-97e1240c90ab",
            "author": "a540bf0f-8b37-4f03-8546-dd71c6b03329",
            "content": "private message.",
            "private": true,
            "pinned": false,
            "updated_at": "2022-12-04T12:49:35.704106+09:00"
        },
        {
            "id": "57649aa5-dfca-4041-9b94-d6b61f5842e7",
            "server": "a7282bea-31d7-4b55-a43e-97e1240c90ab",
            "author": "a540bf0f-8b37-4f03-8546-dd71c6b03329",
            "content": "hello world.",
            "private": false,
            "pinned": false,
            "updated_at": "2022-12-04T12:44:43.248506+09:00"
        }
    ]
}
  • Pinned notes are listed first.
  • Notes are ordered by updated_at in descending order.
  • Private notes are visible only to their authors.

Editing a note

You can edit a note using the note ID.

Request:

PATCH /api/servers/notes/57649aa5-dfca-4041-9b94-d6b61f5842e7/
{
    "content": "hi world.",
    "private": true,
}

The server field cannot be modified.

Pinning a note

You can pin a note to make it appear at the top of the list.

PATCH /api/servers/notes/57649aa5-dfca-4041-9b94-d6b61f5842e7/
{
    "pinned": true,
}

Deleting a note

If a note is no longer needed, you can delete it.

DELETE /api/servers/notes/57649aa5-dfca-4041-9b94-d6b61f5842e7/

Please be aware that the editing or deleting a note is only allowed to following users.

  • Note author
  • Staffs
  • Superusers

Private notes can only be edited or deleted by their respective authors.