[Plugin] USB_Manager


SimonF

Recommended Posts

15 hours ago, SimonF said:

I have just updated to version 2022.07.24 which should fix?

yup, fixed.

 

now my zigbee and rtl sdr dongle that is upstairs connected to a pi are happily and automatically humming along passed through to my hasssio VM in the basement server.

 

thanks for this.

Link to comment

I just wanted to mention a deprecation UX issue that can probably be fixed fairly easily…

 

I got a notification today from Fix Common Problems that usbip-gui had been deprecated. Following the instructions I got to USB Manager, and when I tried to install it, it said it was incompatible with usbip-gui and said “You must uninstall it first” or something like that. It also linked to this comment, which I read and saw, yes, said you must install it—but that the configs were compatible. So I decided I’d make a backup first.

 

I returned to the UnRaid GUI to see where the config was, but first I had to dismiss that message from when I tried to install USB Manager, so I clicked “OK”. Oops. It deleted usbip-gui and installed USB Manager (presumably, deleting the deprecated config at the same time?).

 

I think the problem was that I had an expectation that Unraid’s plugin system doesn’t really make room for: there’s a general consensus in UI that action-gating warnings should never have both an “OK” and “Cancel” button, since it isn’t clear what “OK” means in that context. It’s usually advised to either have a single button to do nothing but acknowledge the warning until the thing being warned about is fixed/forced elsewhere (it doesn’t really matter whether it says “OK” or “Cancel”—whichever makes more sense with the wording of the message), or the buttons should change to “Proceed/Force/Yes, really do it/Delete all my files/etc.” and “Cancel” (preferably with the order of buttons reversed and default swapped, so that someone on autopilot will cancel).

 

(Sometimes an alternative used is an “I understand, proceed” checkbox that must be clicked to make the OK button do something other than cancel.)

 

Simon, given you can’t change that UI, I think you tried to make the best of it with your warning and link, but I think the wording strongly implied that the USB Manager plugin would not install until I had uninstalled usbip-gui first. So I was surprised when it did—hopefully I won’t have unexpected issues as a result.

 

My suggestion would be just to change the wording (I didn’t take a screenshot so I can’t see it now, going from memory) from “You must uninstall usbip-gui first” to something like “If you continue, usbip-gui will be uninstalled. Make a backup of your configuration first: see HERE.”

 

I know that usually you don’t get anything at all when you hit “Install” from the plugins list before installation just happens—but I still think in this case a little more is needed.

 

(If I’m misremembering, and the buttons say “Install” and “Cancel”, or the warning didn’t say ‘you’, then my bad—but it just now happened, so I think I’m recalling correctly.) Thanks!

Edited by TreyH
Link to comment
7 minutes ago, TreyH said:

I just wanted to mention a deprecation UX issue that can probably be fixed fairly easily…

 

I got a notification today from Fix Common Problems that usbip-gui had been deprecated. Following the instructions I got to USB Manager, and when I tried to install it, it said it was incompatible with usbip-gui and said “You must uninstall it first” or something like that. It also linked to this comment, which I read and saw, yes, said you must install it—but that the configs were compatible. So I decided I’d make a backup first.

 

I returned to the UnRaid GUI to see where the config was, but first I had to dismiss that message from when I tried to install USB Manager, so I clicked “OK”. Oops. It deleted usbip-gui and installed USB Manager (presumably, deleting the deprecated config at the same time?).

 

I think the problem was that I had an expectation that Unraid’s plugin system doesn’t really make room for: there’s a general consensus in UI that action-gating warnings should never have both an “OK” and “Cancel” button, since it isn’t clear what “OK” means in that context. It’s usually advised to either have a single button to do nothing but acknowledge the warning until the thing being warned about is fixed/forced elsewhere (it doesn’t really matter whether it says “OK” or “Cancel”—whichever makes more sense with the wording of the message), or the buttons should change to “Proceed/Force/Yes, really do it/Delete all my files/etc.” and “Cancel” (preferably with the order of buttons reversed and default swapped, so that someone on autopilot will cancel).

 

(Sometimes an alternative used is an “I understand, proceed” checkbox that must be clicked to make the OK button do something other than cancel.)

 

Simon, given you can’t change that UI, I think you tried to make the best of it with your warning and link, but I think the wording strongly implied that the USB Manager plugin would not install until I had uninstalled usbip-gui first. So I was surprised when it did—hopefully I won’t have unexpected issues as a result.

 

My suggestion would be just to change the wording (I didn’t take a screenshot so I can’t see it now, going from memory) from “You must uninstall usbip-gui first” to something like “If you continue, usbip-gui will be uninstalled. Make a backup of your configuration first: see HERE.”

 

I know that usually you don’t get anything at all when you hit “Install” from the plugins list before installation just happens—but I still think in this case a little more is needed.

 

(If I’m misremembering, and the buttons say “Install” and “Cancel”, or the warning didn’t say ‘you’, then my bad—but it just now happened, so I think I’m recalling correctly.) Thanks!

Hi @TreyH

 

Where these messages from CA rather than my plugin?

 

Simon.

Link to comment
Just now, SimonF said:

Hi @TreyH

 

Where these messages from CA rather than my plugin?

 

Simon.

They were in the CA interface—but I’m assuming they were part of your plugin since they included that wording with the link to that comment earlier in this thread. If someone else writes the CA warning messages then lmk to whom I should redirect my suggestion. Thanks!

Link to comment
5 minutes ago, TreyH said:

They were in the CA interface—but I’m assuming they were part of your plugin since they included that wording with the link to that comment earlier in this thread. If someone else writes the CA warning messages then lmk to whom I should redirect my suggestion. Thanks!

Suggest that you may want to post on ca support page as it will be Squid that will need to make the changes.

Link to comment

This is what you saw:

 

image.png

 

Now, it's actually nothing to change the OK to be proceed, but what does "Proceed" actually mean?  Hitting OK or "Proceed" goes and does the installation while cancel gets you out of it.

 

I could infer that either Proceed or OK would go ahead and uninstall.  How about instead say "Install" instead of OK

Link to comment
16 minutes ago, SimonF said:

Suggest that you may want to post on ca support page as it will be Squid that will need to make the changes.

Will do, thanks—I had no idea that people other than plugin authors were writing their install manifests on the CA. When I’ve released PyPI/CPAN/Node/Docker/etc. packages onto central install servers I have to do all that stuff myself; I guess the division of labor here is different.

 

But just to be sure I understand before posting—there’s nowhere in your plugin package that you mention the URL https://forums.unraid.net/topic/100511-plugin-usb_manager/page/3/?tab=comments#comment-1002707 or have the words “You must uninstall usbip-gui”—that was written by Squid?

 

Link to comment
4 minutes ago, Squid said:

This is what you saw:

 

image.png

 

Now, it's actually nothing to change the OK to be proceed, but what does "Proceed" actually mean?  Hitting OK or "Proceed" goes and does the installation while cancel gets you out of it.

 

I could infer that either Proceed or OK would go ahead and uninstall.  How about instead say "Install" instead of OK

 

Ah—our messages crossed! Yes, if you can change “OK” to “Proceed” as well, please do! I wasn’t sure the flexibility there. But the main thing would be to change “You must uninstall…” to “USBIP-Gui will be uninstalled in order to use this plugin. See HERE.” With a message to backup the config if it fits as well.

Link to comment
1 minute ago, TreyH said:

But the main thing would be to change “You must uninstall…” to “USBIP-Gui will be uninstalled in order to use this plugin. See HERE

CA will NOT uninstall anything as part of an installation.  So far as I understand, you must uninstall USBIP-GUI manually as the replacement does NOT uninstall it either.  Therefore the message is correct.

Link to comment
3 minutes ago, Squid said:

CA will NOT uninstall anything as part of an installation.  So far as I understand, you must uninstall USBIP-GUI manually as the replacement does NOT uninstall it either.  Therefore the message is correct.

@TreyH Yes I have just check old one is not removed but the new one.

 

old config is not deleted and will be imported into usb manager when installed.

Link to comment
7 minutes ago, Squid said:

CA will NOT uninstall anything as part of an installation.  So far as I understand, you must uninstall USBIP-GUI manually as the replacement does NOT uninstall it either.  Therefore the message is correct.

But it happened. So your understanding must be incorrect.

 

Is there a place later on to view the logs you get in the overlay when you click Install so that I can post that? Poking around I don’t see it. I can tell you I have done absolutely nothing on my server since doing the USB Manager install except type here, and at this moment, my plugin list shows:

IMG_2276.thumb.jpeg.c4cb7e731cf3a8ba8906882a48c85f68.jpeg

 

Note that USB Manager is there, usbip-gui is gone. I can assure you I did not uninstall it—the only reason I was motivated to post here was because I went “nooooo…” when it deleted before I had a chance to backup the config.

Link to comment
6 minutes ago, TreyH said:

But it happened. So your understanding must be incorrect.

 

Is there a place later on to view the logs you get in the overlay when you click Install so that I can post that? Poking around I don’t see it. I can tell you I have done absolutely nothing on my server since doing the USB Manager install except type here, and at this moment, my plugin list shows:

IMG_2276.thumb.jpeg.c4cb7e731cf3a8ba8906882a48c85f68.jpeg

 

Note that USB Manager is there, usbip-gui is gone. I can assure you I did not uninstall it—the only reason I was motivated to post here was because I went “nooooo…” when it deleted before I had a chance to backup the config.

 

9 minutes ago, SimonF said:

Yes I have just check old one is not removed but the new one.

 

old config is not deleted and will be imported into usb manager when installed.

 

@SimonF and @Squid:

 

Ok, I don’t know how to resolve this. I can assure you, with 100% certainty, I did not

1. See the dialog message posted above (“You must uninstall USBIP-GUI…”)

2. Open a second web tab to UnRaid

3. Uninstall usbip-gui

4. Go back to the tab in step 1, and click “Install”

 

But unless I misunderstand you, I hear you saying if I did not do steps 2 and 3 (or the equivalent at the CLI) then 4 should not have worked and/or usbip-gui should still be installed.

 

But this is not the case. What’s going on here? Again, if the install logs are saved somewhere let me know the pathname and I’ll paste it in; I am 95% certain it wrote “uninstalling usbip-gui…” before continuing with the install.

Edited by TreyH
removing dupe quote
Link to comment
31 minutes ago, SimonF said:

What is the output of cat /etc/libvirt/hooks/qemu?

#!/usr/bin/env php

<?php
#begin USB_MANAGER
if ($argv[2] == 'prepare' || $argv[2] == 'stopped'){
  shell_exec("/usr/local/emhttp/plugins/usb_manager/scripts/rc.usb_manager vm_action '{$argv[1]}' {$argv[2]} {$argv[3]} {$argv[4]}  >/dev/null 2>&1 & disown") ;
}
#end USB_MANAGER
if (!isset($argv[2]) || $argv[2] != 'start') {
	exit(0);
}

$strXML = file_get_contents('php://stdin');

$doc = new DOMDocument();
$doc->loadXML($strXML);

$xpath = new DOMXpath($doc);

$args = $xpath->evaluate("//domain/*[name()='qemu:commandline']/*[name()='qemu:arg']/@value");

for ($i = 0; $i < $args->length; $i++){
	$arg_list = explode(',', $args->item($i)->nodeValue);

	if ($arg_list[0] !== 'vfio-pci') {
		continue;
	}

	foreach ($arg_list as $arg) {
		$keypair = explode('=', $arg);

		if ($keypair[0] == 'host' && !empty($keypair[1])) {
			vfio_bind($keypair[1]);
			break;
		}
	}
}

exit(0); // end of script



function vfio_bind($strPassthruDevice) {
	// Ensure we have leading 0000:
	$strPassthruDeviceShort = str_replace('0000:', '', $strPassthruDevice);
	$strPassthruDeviceLong = '0000:' . $strPassthruDeviceShort;

	// Determine the driver currently assigned to the device
	$strDriverSymlink = @readlink('/sys/bus/pci/devices/' . $strPassthruDeviceLong . '/driver');

	if ($strDriverSymlink !== false) {
		// Device is bound to a Driver already

	 	if (strpos($strDriverSymlink, 'vfio-pci') !== false) {
	 		// Driver bound to vfio-pci already - nothing left to do for this device now regarding vfio
	 		return true;
	 	}

 		// Driver bound to some other driver - attempt to unbind driver
 		if (file_put_contents('/sys/bus/pci/devices/' . $strPassthruDeviceLong . '/driver/unbind', $strPassthruDeviceLong) === false) {
			file_put_contents('php://stderr', 'Failed to unbind device ' . $strPassthruDeviceShort . ' from current driver');
			exit(1);
			return false;
 		}
	}

	// Get Vendor and Device IDs for the passthru device
	$strVendor = file_get_contents('/sys/bus/pci/devices/' . $strPassthruDeviceLong . '/vendor');
	$strDevice = file_get_contents('/sys/bus/pci/devices/' . $strPassthruDeviceLong . '/device');

	// Attempt to bind driver to vfio-pci
	if (file_put_contents('/sys/bus/pci/drivers/vfio-pci/new_id', $strVendor . ' ' . $strDevice) === false) {
		file_put_contents('php://stderr', 'Failed to bind device ' . $strPassthruDeviceShort . ' to vfio-pci driver');
		exit(1);
		return false;
	}

	return true;
}

 

Checking the contents of the dir for anything starting with ‘u’ or including ‘usb’:

root@NAS:~# ls /usr/local/emhttp/plugins | grep -i '^u' unRAIDServer/
unRAIDServer/
unassigned.devices/
unassigned.devices-plus/
usb_manager/
user.scripts/
ls /usr
root@NAS:~# ls /local/emhttp/plugins | grep -i 'usb'
usb_manager/

 

Looking at the scripts in usb_manager/, I see there’s a $docroot variable set as 

$docroot = $docroot ?: @$_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';

 

that is later used in ./event/stopping_svcs:

#!/bin/bash
#
DOCROOT=`grep -Po '^chdir = \K.*' /etc/php-fpm.d/www.conf 2>/dev/null`
if [ -z ${DOCROOT} ];then
        DOCROOT="/usr/local/emhttp"
fi

logger "Stopping Daemon(USBIPD)" -t"unraid.usbip-gui"
${DOCROOT}/plugins/usb_manager/scripts/rc.usb_manager stop_daemon

#logger "Detach All USBIP Devices..." -t"unraid.usbip-gui"
#${DOCROOT}/plugins/unraid.usbip-gui/scripts/rc.unraid.usbip-gui detach_ports

 

suggesting to me that if usbip-gui were still installed, I should still have a /usr/local/emhttp/plugins/unraid.usbip-gui directory—but as shown above, I do not.

Link to comment
9 minutes ago, TreyH said:
#!/usr/bin/env php

<?php
#begin USB_MANAGER
if ($argv[2] == 'prepare' || $argv[2] == 'stopped'){
  shell_exec("/usr/local/emhttp/plugins/usb_manager/scripts/rc.usb_manager vm_action '{$argv[1]}' {$argv[2]} {$argv[3]} {$argv[4]}  >/dev/null 2>&1 & disown") ;
}
#end USB_MANAGER
if (!isset($argv[2]) || $argv[2] != 'start') {
	exit(0);
}

$strXML = file_get_contents('php://stdin');

$doc = new DOMDocument();
$doc->loadXML($strXML);

$xpath = new DOMXpath($doc);

$args = $xpath->evaluate("//domain/*[name()='qemu:commandline']/*[name()='qemu:arg']/@value");

for ($i = 0; $i < $args->length; $i++){
	$arg_list = explode(',', $args->item($i)->nodeValue);

	if ($arg_list[0] !== 'vfio-pci') {
		continue;
	}

	foreach ($arg_list as $arg) {
		$keypair = explode('=', $arg);

		if ($keypair[0] == 'host' && !empty($keypair[1])) {
			vfio_bind($keypair[1]);
			break;
		}
	}
}

exit(0); // end of script



function vfio_bind($strPassthruDevice) {
	// Ensure we have leading 0000:
	$strPassthruDeviceShort = str_replace('0000:', '', $strPassthruDevice);
	$strPassthruDeviceLong = '0000:' . $strPassthruDeviceShort;

	// Determine the driver currently assigned to the device
	$strDriverSymlink = @readlink('/sys/bus/pci/devices/' . $strPassthruDeviceLong . '/driver');

	if ($strDriverSymlink !== false) {
		// Device is bound to a Driver already

	 	if (strpos($strDriverSymlink, 'vfio-pci') !== false) {
	 		// Driver bound to vfio-pci already - nothing left to do for this device now regarding vfio
	 		return true;
	 	}

 		// Driver bound to some other driver - attempt to unbind driver
 		if (file_put_contents('/sys/bus/pci/devices/' . $strPassthruDeviceLong . '/driver/unbind', $strPassthruDeviceLong) === false) {
			file_put_contents('php://stderr', 'Failed to unbind device ' . $strPassthruDeviceShort . ' from current driver');
			exit(1);
			return false;
 		}
	}

	// Get Vendor and Device IDs for the passthru device
	$strVendor = file_get_contents('/sys/bus/pci/devices/' . $strPassthruDeviceLong . '/vendor');
	$strDevice = file_get_contents('/sys/bus/pci/devices/' . $strPassthruDeviceLong . '/device');

	// Attempt to bind driver to vfio-pci
	if (file_put_contents('/sys/bus/pci/drivers/vfio-pci/new_id', $strVendor . ' ' . $strDevice) === false) {
		file_put_contents('php://stderr', 'Failed to bind device ' . $strPassthruDeviceShort . ' to vfio-pci driver');
		exit(1);
		return false;
	}

	return true;
}

 

Checking the contents of the dir for anything starting with ‘u’ or including ‘usb’:

root@NAS:~# ls /usr/local/emhttp/plugins | grep -i '^u' unRAIDServer/
unRAIDServer/
unassigned.devices/
unassigned.devices-plus/
usb_manager/
user.scripts/
ls /usr
root@NAS:~# ls /local/emhttp/plugins | grep -i 'usb'
usb_manager/

 

Looking at the scripts in usb_manager/, I see there’s a $docroot variable set as 

$docroot = $docroot ?: @$_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';

 

that is later used in ./event/stopping_svcs:

#!/bin/bash
#
DOCROOT=`grep -Po '^chdir = \K.*' /etc/php-fpm.d/www.conf 2>/dev/null`
if [ -z ${DOCROOT} ];then
        DOCROOT="/usr/local/emhttp"
fi

logger "Stopping Daemon(USBIPD)" -t"unraid.usbip-gui"
${DOCROOT}/plugins/usb_manager/scripts/rc.usb_manager stop_daemon

#logger "Detach All USBIP Devices..." -t"unraid.usbip-gui"
#${DOCROOT}/plugins/unraid.usbip-gui/scripts/rc.unraid.usbip-gui detach_ports

 

suggesting to me that if usbip-gui were still installed, I should still have a /usr/local/emhttp/plugins/unraid.usbip-gui directory—but as shown above, I do not.

Sorry error on my part it does remove the plugin. I think the message was added before it automatically migrated @SquidI think you can remove that message now.

  • Like 1
Link to comment
7 minutes ago, SimonF said:

Sorry error on my part it does remove the plugin. I think the message was added before it automatically migrated @SquidI think you can remove that message now.

Okay, perfect. Yes, that comment link would lead one to believe migration and old-plugin deletion isn’t automatic, so it gave me a scare.

 

Thanks for running this one to ground!

Link to comment
  • 2 weeks later...

Hello,

 

Does anyone have problems with using the driver with USB card in bonding mode? I setup the bonding and during reboot I see " Error 6 calling set_mac_address" message in error log. Tried "active-backup", "balance-tlb" and "balance-alb".

 

Thanks.

Link to comment
9 hours ago, Nenz said:

Hello,

 

Does anyone have problems with using the driver with USB card in bonding mode? I setup the bonding and during reboot I see " Error 6 calling set_mac_address" message in error log. Tried "active-backup", "balance-tlb" and "balance-alb".

 

Thanks.

Hi usb manager cannot helpwith this, but Ido have a qnap 5gb usb port. will check at weekend but i think I could not add to the bond either.

Link to comment
34 minutes ago, SimonF said:

Hi usb manager cannot helpwith this, but Ido have a qnap 5gb usb port. will check at weekend but i think I could not add to the bond either.

Damn it I posted in the wrong browser tab. thank you for the reply and check, will post it in the proper topic ;) now

Link to comment

I added another satellite device to my UNRAID box and now I am getting this message in the logs constantly..

 

Any ideas?

 

Aug 12 14:00:16 UNRAID kernel: vhci_hcd: unlink->seqnum 108674182
Aug 12 14:00:16 UNRAID kernel: vhci_hcd: urb->status -104
Aug 12 14:00:22 UNRAID kernel: vhci_hcd: unlink->seqnum 108700222
Aug 12 14:00:22 UNRAID kernel: vhci_hcd: urb->status -104
Aug 12 14:00:28 UNRAID kernel: vhci_hcd: unlink->seqnum 108727607
Aug 12 14:00:28 UNRAID kernel: vhci_hcd: urb->status -104
Aug 12 14:00:34 UNRAID kernel: vhci_hcd: unlink->seqnum 108755739
Aug 12 14:00:34 UNRAID kernel: vhci_hcd: urb->status -104
Aug 12 14:00:40 UNRAID kernel: vhci_hcd: unlink->seqnum 108782401
Aug 12 14:00:40 UNRAID kernel: vhci_hcd: urb->status -104
Aug 12 14:00:46 UNRAID kernel: vhci_hcd: unlink->seqnum 108810661
Aug 12 14:00:46 UNRAID kernel: vhci_hcd: urb->status -104
Aug 12 14:00:52 UNRAID kernel: vhci_hcd: unlink->seqnum 108838662
Aug 12 14:00:52 UNRAID kernel: vhci_hcd: urb->status -104
Aug 12 14:00:58 UNRAID kernel: vhci_hcd: unlink->seqnum 108866563
Aug 12 14:00:58 UNRAID kernel: vhci_hcd: urb->status -104

 

Link to comment
19 minutes ago, muzo178 said:

I added another satellite device to my UNRAID box and now I am getting this message in the logs constantly..

 

Any ideas?

 

Aug 12 14:00:16 UNRAID kernel: vhci_hcd: unlink->seqnum 108674182
Aug 12 14:00:16 UNRAID kernel: vhci_hcd: urb->status -104
Aug 12 14:00:22 UNRAID kernel: vhci_hcd: unlink->seqnum 108700222
Aug 12 14:00:22 UNRAID kernel: vhci_hcd: urb->status -104
Aug 12 14:00:28 UNRAID kernel: vhci_hcd: unlink->seqnum 108727607
Aug 12 14:00:28 UNRAID kernel: vhci_hcd: urb->status -104
Aug 12 14:00:34 UNRAID kernel: vhci_hcd: unlink->seqnum 108755739
Aug 12 14:00:34 UNRAID kernel: vhci_hcd: urb->status -104
Aug 12 14:00:40 UNRAID kernel: vhci_hcd: unlink->seqnum 108782401
Aug 12 14:00:40 UNRAID kernel: vhci_hcd: urb->status -104
Aug 12 14:00:46 UNRAID kernel: vhci_hcd: unlink->seqnum 108810661
Aug 12 14:00:46 UNRAID kernel: vhci_hcd: urb->status -104
Aug 12 14:00:52 UNRAID kernel: vhci_hcd: unlink->seqnum 108838662
Aug 12 14:00:52 UNRAID kernel: vhci_hcd: urb->status -104
Aug 12 14:00:58 UNRAID kernel: vhci_hcd: unlink->seqnum 108866563
Aug 12 14:00:58 UNRAID kernel: vhci_hcd: urb->status -104

 

Based on the errcode 104 ECONNRESET Connection reset by peer

Link to comment
11 minutes ago, aim60 said:

Knowing full well that you may not want to pollute the code of USB Manager ...

 

I was looking for a way to hot plug a vdisk or iso file into a running VM.  And I thought that USB Manager may already have most of the plumbing to attach them as USB devices.

I haven't create any change for this yet in VM Manager is this the type of thing you are looking for?

 

 

Link to comment
20 hours ago, SimonF said:

is this the type of thing you are looking for

virsh change-media seems to work for changing the iso in a cdrom drive that has been pre-defined in the VM.  I was thinking of USB Manager creating virtual usb ports with associated iso or vdisk files.  They could be hot-plugged into the VM as usb devices with VM Attach/Detach.  The advantage of implementing this in USB Manager is that commonly used iso or vdisk "ports" could be pre-defined, and switched between VMs as desired.  The advantage of implementing hot-plug in VM Manager is that not everyone has discovered this plugin.

Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.