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 seen by alpacon
  • status: Detailed description about the server status in json format.
    • code: ok if status is good, warn or error if something is wrong.
    • text: A description about the code. Ignore this field if the client implements its own description.
    • icon, color: Icon and color to show the status. Ignore this field if the client implements its own icons. This fields can be used like <i class="text-{{ object.status.color }} fa-solid fa-{{ object.status.icon }}"></i>
    • messages: The list of detailed messages that show server status. If errors are found, they are listed here.
    • meta: Use delay_now in meta to display current delay of a server.
  • is_connected: Whether alpamon is connected now
  • commissioned: Whether alpamon has reported the system status
  • starred: Whether alpamon is starred
  • version: alpamon version
  • osquery_version: osquery version (osquery is used by alpamon to gather system information)
  • uptime: Time since the system has booted
  • boot_time: Time when the system booted
  • last_connectivity: Time when alpamon last contacted
  • started_at: Time when alpamon started
  • added_at: Time when alpamon was added
  • updated_at: Time when alpamon was updated
  • user: User who added this 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 also list system packages with the following API. This API provides more detailed query arguments and supports 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 with id field set 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 to access servers remotely. Should clients use the following API to start 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 that can be used to access 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 time 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 transferring files 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: A file content for uploading. A multipart form should be submitted as JSON does not support binary files.
    • path: Destination path for the file. If omitted, your home directory will be used as default.
    • server: Server instance id (It should be a uuid, not a name). You should have valid permission on the server.
  • Response data: A file upload instance. For now, it does not include any meaningful result.
  • Status codes: 201 CREATED on success.

When POST finishes successfully, alpacon will transfer the file to alpamon. We are planning to report the transfer status via API.

Cancelling transfer

(🚧 Working in Progress) You can cancel a transfer request before it actually reaches to the 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 finished successfully, alpacon will execute it via alpamon. Corresponding command instance will be returned. You can check the result later via GET /api/events/commands/<id>/.

Running an arbitrary command

You can run an arbitrary command on the system that alpamon is installed. Your commands will run with your account privilege.

Request:

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

As you can see in the example, shell, line, and server are mandatory fields. data and scheduled_at 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. This field only works for 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": []
}

If you are going to test a scheduled command (commands that will run in the future), you need to execute celery background workers. For more about how to run celery, you may refer to Alpacon README (opens in a new tab).

For the information about each response fields, please refer to the next section of 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, please 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/alpacanetworks/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 is null until the command is executed.
  • result: The output result of the command. This field is null until the command is executed.
  • status: A dictionary showing the status of command
    • 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: Whether this command can be cancelled. A command can be cancelled if it has not 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 acked by the server, this field will be null.
  • elapsed_time: The time taken to run the command. If the command has not run, this field will be null.
  • scheduled_at: The scheduled time for this command. If this field is not specified, the command will be executed immediately.
  • delivered_at: The time that alpacon sent this command to alpamon.
  • acked_at: The time that alpamon acknowledged the command. The response delay means acked_at - delivered_at.
  • handled_at: The time that alpamon finished executing the command. The elapsed time means handled_at - acked_at.
  • server: The id of the server targeted by this command.
  • requested_by: The id of the operator who requested the command.
  • run_after: The 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 at 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/alpacanetworks/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/alpacanetworks/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 a note to a server so that they can memorize some details about it. Notes can be private or pinned.

Making a note

Use POST to leave a 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 list notes for a 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 only visible to the 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,
}

You can't alter server field.

Pinning a note

You can pin a note so that they can show up at first in the list.

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

Deleting a note

If the note is not needed, you can delete a note.

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 the note author.