Creating standalone qube from commandline

I am trying to set up a proper saltstack conf and I’m stuck on creating a standalone qube.

First off all it seems like the docs are slightly incorrect because they claim you control the type through the type keyword however class is the only thing that works for me.

Secondly when I create a standalone class it causes an error, when I investigate this and run the command manually I get the same error.

qvm-create qube-name --class=standalone --label=red results in
app: Error creating VM: Got empty response from qubesd. See journalctl in dom0 for details.
There are no details beyond there being a “protocol error”.

I’m somewhat suspecting that the error has to do with the fact that I am not specifying what file system the qube should have. Normally when you create a standalone qube through the GUI you select another qube to copy. I can’t see any obvious argument which does that, template doesn’t. I suspect it might be the --root-copy-from but I don’t know how to get a filesystem as a file in the appropriate format.

Furthermore I don’t know how to specify root-copy-from in my saltstack conf.

Instead of doing:

<statename>:
    qvm.present:
        - name: <appvm name>
        - template: <template name>
        - label:  <color>

like you would for an AppVm (or disposable, or disposable template which is really an appVM), do this:

<statename>:
    qvm.clone:
        - name: <your new template name>
        - source: <the template you want to copy into your new template>

The key differences are you’re doing a qvm.clone, not a qvm.present, and instead of a template, you have a source (the clone-ee).

If I do that then won’t the cloned VM be a template VM rather than a standalone VM? Is there a way to achieve what you accomplish via the GUI when creating a standalone?

That’s a very good question, and I have to apologize for losing sight of the fact that you were talking about a standalone.

It’s possible that if “source” is a standalone, then you can use this method and the new qube will be too. (Nothing limits clones to templates.) I’m not in a position to try that experiment, though. If you are trying to clone a standalone, please try it and see what happens.

You are reading that wrong - that section refers to keywords in the
pillar, distinct from use in your own modules and states.

Check out qvm-create --help which will lead you to qvm-create --help-classes
What you want is qvm-create --class=StandaloneVM --label=red --template=TEMPLATE....... QUBE-NAME

In salt, you would use:

create_standalone:
  qvm.present:
    - name: QUBE-NAME
    - template: TEMPLATE
    - label: red
    - flags:
      - standalone

Like this:

copy_filesystem:
  qvm.present:
    - name: QUBE-NAME
    - label: red
    - root-copy-from: PATH
1 Like

This seems to be what I’m looking for, however I’m getting a invalid property 'template' of salt-standalone when using the following configuration. Note that I’m not getting that error for salt-no-netvm. Any idea why? Are there any other comments on how I should do my configuration?

{% set fedora_app_vms = [
  {
    'name': 'salt-standalone',
    'label': 'red',
    'template': 'fedora-36',
    'type': 'standalone'
  },
  {
    'name': 'salt-no-netvm',
    'label': 'blue',
    'template': 'debian-11',
    'netvm': 'none'
  },
] %}

{% for vm in fedora_app_vms %}
Check if {{ vm.name }} exists:
  cmd.run:
    - name: qvm-check {{ vm.name }} || echo "__QUBE_NOT_FOUND__"
    - shell: /bin/bash
    - output: qube_exists_result
    - changes_dict: {'qube_exists_result': '{{ vm.name }}'}

Create {{ vm.name }}:
  qvm.present:
    - name: {{ vm.name }}
    - label: {{ vm.label }}
    - mem: 2000
    - vcpus: 4
    {% if 'template' in vm %}
    - template: {{ vm.template }}
    {% endif %}
    {% if 'type' in vm and vm.type == 'standalone' %}
    - flags:
      - standalone
    {% endif %}

Configure {{ vm.name }}:
  qvm.prefs:
    - name: {{ vm.name }}
    {% if 'netvm' in vm %}
    - netvm: {{ vm.netvm }}
    {% endif %}
    - onchanges:
      - cmd: Check if {{ vm.name}} exists
{% endfor %}

I’ve never used saltstack before and I realized that there were some qubes specific macros in the standard qubes configurations, is it good practice to use those?

Is there any place I can read more about which flags are available?

There are indeed qubes specific macros, but I think you should get the
basics first before hand.
There is good documentation at qubes-mgmt-salt-dom0-qvm/README.rst at master · QubesOS/qubes-mgmt-salt-dom0-qvm · GitHub
and of course, the source code under /srv/salt
Use the salt forms instead of calling external programs - qvm.exists,
for example, instead of a call to qvm-check.

I never presume to speak for the Qubes team. When I comment in the Forum or in the mailing lists I speak for myself.

A suggestion: that github info is a lot more complete than what’s in the qubes-os.org documentation page. (I did not know, for instance, that you could do qvm.features until just now; I was writing calls to qvm-features.) Could that info possibly make its way onto that page?

It is directly linked from the Qubes introduction page.

There is a link at the bottom of this page: Salt (management software) | Qubes OS but every link there is to the salt official site. I really think that link should be near where the (subset) of qubes-specific sets is discussed, with an explanation to the effect that the link points to a complete list; what’s shown here is just a set of examples. Otherwise one gets the impression that’s a complete listing. (At least one does, if one is SteveC.)

Let me redo that post; the first sentence was just a bodge (I would edit, but unman is an email user).

There is a link at the bottom of this page: Salt (management software) | Qubes OS but every link in the main text of the page is to the salt official site.

I really think that link at the bottom of the page should be near where the (subset) of qubes-specific sets is discussed, with an explanation to the effect that the link points to a complete list; what’s shown here is just a set of examples. Otherwise one gets the impression that’s a complete listing. (At least one does, if one is SteveC.) Basically “additional reading” implies it’s optional; in this case it’s a vital reference for anyone doing salt.

The salt pages definitely need work.

Hi, sorry for the really long lurk.

I’ve returned to this project and encountered my problem again however now I’ve solved it. Yet I’m puzzled by why the solution works. I’ll show my code and my error. It turns out adding a line for 'label': {{ vm.label }} solves my issue but I’m unclear why.

This is the output

$ sudo qubesctl state.highstate
[ERROR   ] An exception occurred in this state: Traceback (most recent call last):
  File "/usr/lib/python3.8/site-packages/salt/state.py", line 2153, in call
    ret = self.states[cdata["full"]](
  File "/usr/lib/python3.8/site-packages/salt/loader.py", line 2087, in wrapper
    return f(*args, **kwargs)
  File "/var/cache/salt/minion/extmods/states/ext_state_qvm.py", line 293, in prefs
    return _state_action('qvm.prefs', name, *varargs, **kwargs)
  File "/var/cache/salt/minion/extmods/states/ext_state_qvm.py", line 137, in _state_action
    status = __salt__[_action](*varargs, **kwargs)
  File "/var/cache/salt/minion/extmods/modules/ext_module_qvm.py", line 876, in prefs
    elif args.vm.property_is_default(dest):
  File "/usr/lib/python3.8/site-packages/qubesadmin/base.py", line 163, in property_is_default
    property_str = self.qubesd_call(
  File "/usr/lib/python3.8/site-packages/qubesadmin/base.py", line 76, in qubesd_call
    return self.app.qubesd_call(dest, method, arg, payload,
  File "/usr/lib/python3.8/site-packages/qubesadmin/app.py", line 765, in qubesd_call
    return self._parse_qubesd_response(return_data)
  File "/usr/lib/python3.8/site-packages/qubesadmin/base.py", line 111, in _parse_qubesd_response
    raise exc_class(format_string, *args)
qubesadmin.exc.QubesNoSuchPropertyError: Invalid property 'template' of salt-standalone

local:
----------
          ID: topd-always-passes
    Function: test.succeed_without_changes
        Name: foo
      Result: True
     Comment: Success!
     Started: 18:07:44.996164
    Duration: 0.624 ms
     Changes:   
----------
          ID: Check if salt-standalone exists
    Function: cmd.run
        Name: qvm-check salt-standalone || echo "__QUBE_NOT_FOUND__"
      Result: True
     Comment: Command "qvm-check salt-standalone || echo "__QUBE_NOT_FOUND__"" run
     Started: 18:07:44.997450
    Duration: 146.018 ms
     Changes:   
              ----------
              pid:
                  41338
              retcode:
                  0
              stderr:
                  qvm-check: salt-standalone: exists
              stdout:
----------
          ID: Create salt-standalone
    Function: qvm.present
        Name: salt-standalone
      Result: True
     Comment: [SKIP] A VM with the name 'salt-standalone' already exists. None
     Started: 18:07:45.181341
    Duration: 275.344 ms
     Changes:   
----------
          ID: Configure salt-standalone
    Function: qvm.prefs
        Name: salt-standalone
      Result: False
     Comment: An exception occurred in this state: Traceback (most recent call last):
                File "/usr/lib/python3.8/site-packages/salt/state.py", line 2153, in call
                  ret = self.states[cdata["full"]](
                File "/usr/lib/python3.8/site-packages/salt/loader.py", line 2087, in wrapper
                  return f(*args, **kwargs)
                File "/var/cache/salt/minion/extmods/states/ext_state_qvm.py", line 293, in prefs
                  return _state_action('qvm.prefs', name, *varargs, **kwargs)
                File "/var/cache/salt/minion/extmods/states/ext_state_qvm.py", line 137, in _state_action
                  status = __salt__[_action](*varargs, **kwargs)
                File "/var/cache/salt/minion/extmods/modules/ext_module_qvm.py", line 876, in prefs
                  elif args.vm.property_is_default(dest):
                File "/usr/lib/python3.8/site-packages/qubesadmin/base.py", line 163, in property_is_default
                  property_str = self.qubesd_call(
                File "/usr/lib/python3.8/site-packages/qubesadmin/base.py", line 76, in qubesd_call
                  return self.app.qubesd_call(dest, method, arg, payload,
                File "/usr/lib/python3.8/site-packages/qubesadmin/app.py", line 765, in qubesd_call
                  return self._parse_qubesd_response(return_data)
                File "/usr/lib/python3.8/site-packages/qubesadmin/base.py", line 111, in _parse_qubesd_response
                  raise exc_class(format_string, *args)
              qubesadmin.exc.QubesNoSuchPropertyError: Invalid property 'template' of salt-standalone
     Started: 18:07:45.458052
    Duration: 80.143 ms
     Changes:   
----------
          ID: Check if salt-no-netvm exists
    Function: cmd.run
        Name: qvm-check salt-no-netvm || echo "__QUBE_NOT_FOUND__"
      Result: True
     Comment: Command "qvm-check salt-no-netvm || echo "__QUBE_NOT_FOUND__"" run
     Started: 18:07:45.538369
    Duration: 132.424 ms
     Changes:   
              ----------
              pid:
                  41360
              retcode:
                  0
              stderr:
                  qvm-check: salt-no-netvm: exists
              stdout:
----------
          ID: Create salt-no-netvm
    Function: qvm.present
        Name: salt-no-netvm
      Result: True
     Comment: [SKIP] A VM with the name 'salt-no-netvm' already exists. None
     Started: 18:07:45.671382
    Duration: 272.96 ms
     Changes:   
----------
          ID: Configure salt-no-netvm
    Function: qvm.prefs
        Name: salt-no-netvm
      Result: True
     Comment: [SKIP] netvm              : None None
     Started: 18:07:45.945568
    Duration: 48.539 ms
     Changes:   

Summary for local
------------
Succeeded: 6 (changed=2)
Failed:    1
------------
Total states run:     7
Total run time: 956.052 ms
DOM0 configuration failed, not continuing

This is the code I’m running:

{% set fedora_app_vms = [
  {
    'name': 'salt-standalone',
    'label': 'red',
    'template': 'fedora-36',
    'type': 'standalone'
  },
  {
    'name': 'salt-no-netvm',
    'label': 'blue',
    'template': 'debian-11',
    'netvm': 'none'
  },
] %}

{% for vm in fedora_app_vms %}
Check if {{ vm.name }} exists:
  cmd.run:
    - name: qvm-check {{ vm.name }} || echo "__QUBE_NOT_FOUND__"
    - shell: /bin/bash
    - output: qube_exists_result
    - changes_dict: {'qube_exists_result': '{{ vm.name }}'}

Create {{ vm.name }}:
  qvm.present:
    - name: {{ vm.name }}
    - label: {{ vm.label }}
    - mem: 2000
    - vcpus: 4
    {% if 'template' in vm %}
    - template: {{ vm.template }}
    {% endif %}
    {% if 'type' in vm and vm.type == 'standalone' %}
    - flags:
      - standalone
    {% endif %}

Configure {{ vm.name }}:
  qvm.prefs:
    - name: {{ vm.name }}
    {% if 'netvm' in vm %}
    - netvm: {{ vm.netvm }}
    {% endif %}
    - onchanges:
      - cmd: Check if {{ vm.name}} exists
{% endfor %}

Adding a line to the configuration function(?) so it looks like this:

Configure {{ vm.name }}:
  qvm.prefs:
    - name: {{ vm.name }}
    - label: {{ vm.label }}
    {% if 'netvm' in vm %}
    - netvm: {{ vm.netvm }}
    {% endif %}
    - onchanges:
      - cmd: Check if {{ vm.name}} exists

magically solves the problem. Why?