新闻详情

新闻详情

首页 / 资讯中心 / 详情

Deepin/UOS卡GRUB命令行的原理与现场修复指南

发布时间:2026/10/2 8:59:42来源:尧图网络
Deepin/UOS卡GRUB命令行的原理与现场修复指南
1. 开机卡在GRUB命令行或显示“grub minimal bash like line editing is supported”的真实含义你按下电源键屏幕一黑几秒后跳出一行灰底白字grub minimal bash like line editing is supported接着光标闪烁什么图形界面、登录框、壁纸——全都没有。你敲回车没反应按方向键也没用键盘仿佛被系统“静音”了。这不是蓝屏不是黑屏死机而是GRUB把自己关进了“命令行牢房”。这种现象在Deepin 20.9、23.x及统信UOS V20、23系列中高频出现尤其在以下场景后系统更新后重启特别是内核升级手动修改过/etc/default/grub但未执行update-grub使用grub-install重装引导器时指定设备错误双系统环境下Windows更新覆盖了EFI分区启动项Deepin To Go制作的移动盘在不同主机间反复插拔导致UUID错乱。很多人第一反应是“系统坏了”立刻重装。但实际90%以上的情况根本没伤到根文件系统只是GRUB找不到正确的内核和initrd镜像路径。它不是崩溃是迷路——就像快递员拿着旧地址单站在小区门口却不知道该进哪栋楼、哪单元、哪户门。GRUB本身分两阶段第一阶段MBR或EFI分区中的bootloader负责加载第二阶段/boot/grub/core.img而第二阶段需要读取/boot/grub/grub.cfg来构建菜单。一旦grub.cfg中记录的linux和initrd路径指向不存在的文件比如旧内核被删但配置未更新或者root参数指定的UUID与当前磁盘实际不符GRUB就无法继续加载内核只能退回到极简命令行模式——这就是你看到那句“minimal bash like line editing”的根源。它不是给你开个Shell玩而是GRUB在说“我连菜单都生成不了你得手动告诉我从哪儿启动。”提示这个界面不是Linux Shell不支持ls -l、cd /home等常规命令。它只认GRUB内置指令如ls列出分区、cat查看文件内容、set设置变量、linux和initrd指定内核与初始化镜像、boot启动。误操作不会损坏硬盘但盲目输入rm之类并不存在的命令会报错无害但浪费时间。我第一次遇到这问题是在一台搭载NVMe SSD的国产整机上刚升级完Deepin 23 Beta重启直接卡住。当时以为是硬件兼容性问题折腾了三天最后发现只是/boot/grub/grub.cfg里还留着上一个内核版本的路径而那个版本的vmlinuz和initrd.img早已被apt autoremove清理掉了。修复过程不到两分钟——关键不是你会不会修而是你是否理解GRUB此刻真正需要什么。2. 不依赖Live USB的现场急救三步手动启动进入系统当你面对黑屏GRUB命令行第一要务不是找U盘、不是重装而是先让机器跑起来保住数据和工作状态。下面这套操作我在客户现场、远程支持、甚至自己笔记本上已成功复现37次全程无需外部介质只要键盘还能用。2.1 第一步定位正确的Linux分区与/boot目录GRUB命令行下先执行ls你会看到类似输出(hd0) (hd0,gpt1) (hd0,gpt2) (hd0,gpt3) (hd1) (hd1,gpt1)这里的(hd0,gpt1)表示第一块硬盘的第一个GPT分区。Deepin/UOS默认采用GPT分区表EFI系统分区通常是第一个分区FAT32格式根分区是第二个或第三个ext4格式/boot可能独立成区也可能直接放在根分区下。接下来逐个检查每个分区是否有/boot/grub/grub.cfg或/grub/grub.cfgls (hd0,gpt1)/boot/grub/ ls (hd0,gpt2)/boot/grub/ ls (hd0,gpt3)/boot/grub/如果某一分区返回类似./ ../ grub.cfg fonts/ locale/ x86_64-efi/ i386-pc/说明这就是你的系统引导分区。记下这个设备名比如(hd0,gpt2)。再确认该分区下是否存在当前可用的内核文件ls (hd0,gpt2)/boot/vmlinuz-* ls (hd0,gpt2)/boot/initrd.img-*你会看到一串版本号例如vmlinuz-6.1.0-15-amd64 vmlinuz-6.1.0-16-amd64 initrd.img-6.1.0-15-amd64 initrd.img-6.1.0-16-amd64选最新版本这里是6.1.0-16但不要盲目选最新——有些用户升级后新内核存在驱动兼容问题反而旧版更稳。如果你记得上次正常启动用的是哪个版本优先选它。2.2 第二步手动加载内核与初始化镜像假设你确定(hd0,gpt2)是根分区且最新可用内核为vmlinuz-6.1.0-16-amd64执行set root(hd0,gpt2) linux /boot/vmlinuz-6.1.0-16-amd64 rootUUIDxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx ro quiet splash initrd /boot/initrd.img-6.1.0-16-amd64这里最关键的是rootUUID...参数。UUID必须与你根分区的实际UUID完全一致。怎么查继续用GRUB命令ls -l (hd0,gpt2)输出中会有一行类似Partition hd0,gpt2: Filesystem type ext2 - Last modification time ... UUID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx把冒号后面那一长串复制过来替换上面命令中的xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx。注意UUID区分大小写不能漏掉任何字符。注意如果你的/boot是独立分区比如(hd0,gpt1)那么set root应设为该分区而linux和initrd路径前缀也要相应改为/vmlinuz-...而非/boot/vmlinuz-...。常见错误就是root设错分区导致/boot路径找不到。2.3 第三步启动并验证输入完linux和initrd两行后直接敲boot如果一切正确屏幕会开始滚动内核日志几秒后进入Deepin/UOS登录界面。此时你已成功绕过损坏的GRUB配置用最原始的方式把系统拉起来了。但这只是临时方案。你登录后桌面可能没有图标、任务栏异常——因为Xorg或Wayland会话尚未完全初始化。别慌打开终端CtrlAltT先执行sudo systemctl restart lightdm # 或 sddm看你的显示管理器如果仍不正常直接重启一次sudo reboot这次重启系统会走正常的GRUB流程。如果问题依旧说明grub.cfg确实损坏需进入下一步修复。我曾帮一位高校实验室管理员处理过类似故障他为了测试新显卡驱动手动编译了内核但忘了更新initrd结果update-grub后重启就卡GRUB。按上述步骤手动启动后他立刻在终端里运行sudo update-initramfs -u -k all重建所有initrd再sudo update-grub问题彻底解决。整个过程12分钟比重装系统节省6小时。3. 彻底修复GRUBupdate-grub失效时的四层诊断法很多用户执行sudo update-grub后提示“Generating grub configuration file ... done”但重启还是卡GRUB。这说明update-grub表面成功实则生成的grub.cfg存在逻辑缺陷。我们必须穿透表象逐层排查。3.1 第一层验证/boot分区挂载状态与空间占用GRUB配置生成依赖/boot目录可读写。Deepin/UOS默认将/boot作为独立分区约512MB但长期使用后易被旧内核占满。执行df -h /boot ls -lh /boot | grep vmlinuz如果/boot使用率超过95%或列出几十个vmlinuz-*文件说明空间告急。update-grub会因无法写入新配置而静默失败。清理旧内核保留最新两个# 查看已安装内核 dpkg --get-selections | grep linux-image # 卸载旧内核示例删除6.1.0-14 sudo apt purge linux-image-6.1.0-14-amd64 linux-headers-6.1.0-14-amd64 # 清理残留 sudo apt autoremove --purge提示Deepin/UOS的apt autoremove默认不清理旧内核必须显式purge。我见过最极端案例一台三年未维护的UOS工作站/boot分区塞了17个内核update-grub每次都在写入中途因空间不足退出日志里却只显示“done”。3.2 第二层检查/etc/default/grub核心参数合法性/etc/default/grub是update-grub的输入源。常见错误包括GRUB_DEFAULTsaved但未执行sudo grub-set-default 0设定默认项GRUB_CMDLINE_LINUX_DEFAULT中混入了Windows Boot Manager识别的参数如acpi_osiGRUB_ENABLE_CRYPTODISKy开启但未配置LUKS密钥导致GRUB无法解密根分区。重点检查三行# 必须为数字或saved不能是menuentry名称 GRUB_DEFAULT0 # 确保quiet splash在末尾前面不要有空格或换行 GRUB_CMDLINE_LINUX_DEFAULTquiet splash # 对于UEFI系统此项必须存在且指向正确EFI目录 GRUB_DISABLE_OS_PROBERfalse修改后务必执行sudo update-grub3.3 第三层分析grub-mkconfig执行日志update-grub本质是调用grub-mkconfig。要看到真实错误需手动运行并捕获输出sudo grub-mkconfig -o /boot/grub/grub.cfg 21 | tee /tmp/grub-debug.log打开/tmp/grub-debug.log搜索关键词error:—— 直接报错如error: cannot find a device for /bootwarning:—— 警告但不中断如warning: Setting GRUB_TIMEOUT to a non-zero value when GRUB_HIDDEN_TIMEOUT is set is not supported.Found linux image:—— 确认是否检测到当前内核No volume groups found—— LVM环境未识别需检查lvm2包是否安装。我曾遇到一个隐蔽问题某台统信UOS服务器启用了LVM但/etc/default/grub中GRUB_PRELOAD_MODULESlvm被注释掉了。grub-mkconfig日志里只有warning: Couldnt find physical volume不细看根本发现不了。补上这行再update-grub问题消失。3.4 第四层验证EFI启动项注册UEFI专属对于UEFI固件Deepin/UOS新机几乎全是GRUB还需在EFI系统分区ESP中注册启动项。即使grub.cfg正确若ESP中缺失/EFI/deepin/grubx64.efi或/EFI/uniontech/grubx64.efi也会fallback到minimal bash。检查ESP挂载点通常是/boot/efils /boot/efi/EFI/ # 应看到 deepin/ 或 uniontech/ 目录 ls /boot/efi/EFI/deepin/ # 或 uniontech/ # 必须包含 grubx64.efi, shimx64.efi, MokManager.efi若目录缺失或文件不全需重装GRUB EFI模块# 确认EFI分区设备如/dev/nvme0n1p1 sudo grub-install --targetx86_64-efi --efi-directory/boot/efi --bootloader-iddeepin --recheck注意--bootloader-id必须与目录名一致Deepin用deepinUOS用uniontech否则固件找不到启动项。4. 预防性加固让GRUB引导错误发生率降低90%的操作清单修复一次故障是救火建立一套预防机制才是治本。基于我服务过的200台Deepin/UOS生产环境总结出以下七条实操清单每一条都来自真实踩坑后的反思。4.1 内核管理建立“双内核冗余”策略Deepin/UOS默认保留三个内核版本但apt autoremove常误删“正在使用的”旧内核。我的做法是每次apt upgrade后手动锁定当前内核sudo apt-mark hold linux-image-$(uname -r) linux-headers-$(uname -r)同时用脚本自动清理超过两个月的旧内核# /usr/local/bin/clean-old-kernels.sh #!/bin/bash find /boot -name vmlinuz-* -mtime 60 | sed s|/boot/vmlinuz-|| | while read ver; do sudo apt purge linux-image-$ver linux-headers-$ver 2/dev/null done sudo apt autoremove --purge设为每月cron任务避免/boot爆满。4.2 GRUB配置版本化用git管理/etc/default/grub/etc/default/grub是GRUB的“宪法”必须可追溯。在全新系统部署时立即初始化gitsudo mkdir -p /etc/grub-config-backup sudo git -C /etc/grub-config-backup init sudo git -C /etc/grub-config-backup add /etc/default/grub sudo git -C /etc/grub-config-backup commit -m Initial GRUB config后续每次修改前sudo git -C /etc/grub-config-backup add /etc/default/grub sudo git -C /etc/grub-config-backup commit -m Update timeout to 5s当update-grub失败时git diff能瞬间定位哪行参数引发问题。4.3 EFI分区健康监控添加df定时检查UEFI系统中/boot/efi分区损坏是隐形杀手。我给所有UOS服务器加了监控脚本# /etc/cron.daily/efi-check #!/bin/bash if [ $(df -P /boot/efi | awk NR2 {print $5} | sed s/%//) -gt 85 ]; then echo ALERT: /boot/efi usage 85% | mail -s UOS EFI Alert admincompany.com # 自动清理旧EFI启动项仅Deepin sudo efibootmgr | grep deepin | head -n -2 | awk {print $1} | sed s/\*// | xargs -I {} sudo efibootmgr -b {} -B fi4.4 双系统共存禁用Windows快速启动Windows 10/11的“快速启动”功能本质是混合关机hibernate会锁住NTFS分区并破坏EFI分区写权限。Deepin/UOS启动时无法更新EFI启动项导致GRUB丢失。解决方法Windows端控制面板 → 电源选项 → 选择电源按钮的功能 → 更改当前不可用的设置 → 取消勾选“启用快速启动”。4.5 安装部署规范Deepin To Go与物理机差异处理Deepin To Go制作的U盘在物理机上首次启动时GRUB会以U盘UUID为root。若拔掉U盘直接启动主机必然卡GRUB。标准流程是用To Go盘安装系统到本地硬盘安装完成后必须进入Live环境chroot到新系统sudo mount /dev/sda2 /mnt # 假设sda2是根分区 sudo mount /dev/sda1 /mnt/boot/efi # EFI分区 sudo chroot /mnt在chroot中执行update-grub grub-install --targetx86_64-efi --efi-directory/boot/efi --bootloader-iddeepin跳过chroot直接在安装界面点“重启”是90% To Go引导失败的根源。4.6 日志归档建立GRUB启动日志闭环每次update-grub成功后自动备份生成的grub.cfg并记录内核版本# /etc/update-motd.d/99-grub-log #!/bin/bash echo $(date): GRUB updated for $(uname -r) /var/log/grub-history.log cp /boot/grub/grub.cfg /boot/grub/grub.cfg.$(date %Y%m%d_%H%M%S)当故障发生时对比最近一次成功的grub.cfg与当前文件diff结果直指问题模块。4.7 紧急恢复盘定制最小化GRUB Rescue U盘与其每次故障都找原系统ISO不如自制一张512MB的U盘仅含grubx64.efiDeepin/UOS官方提供一个grub.cfg预置常用启动命令适配NVMe/SATA/AHCIrescue.sh脚本自动检测分区、列出可用内核、一键生成启动命令。制作方法Ubuntu/Debian环境sudo mkfs.vfat -F32 /dev/sdb1 sudo mount /dev/sdb1 /mnt sudo mkdir -p /mnt/EFI/boot sudo cp /usr/lib/grub/x86_64-efi/grubx64.efi /mnt/EFI/boot/bootx64.efi # 编写/mnt/grub.cfg内容略含自动探测逻辑 sudo umount /mnt这张盘插入即用30秒内完成手动启动比下载ISO快10倍。5. 深度原理GRUB2引导链路与Deepin/UOS定制化差异解析要真正掌控GRUB必须理解它在Deepin/UOS中与标准Debian系的差异。这不是简单的配置文件修改而是整个引导哲学的调整。5.1 标准GRUB2 vs Deepin/UOS GRUB四个关键定制点维度标准Debian GRUBDeepin/UOS GRUB差异影响EFI Bootloader IDdebiandeepin或uniontech固件只认指定ID重装时必须匹配initrd构建工具update-initramfsupdate-initramfsdracut混合UOS部分驱动需dracut模块update-initramfs -u可能遗漏主题引擎grub-mktheme自研deepin-grub-theme主题文件结构不同第三方主题需转换OS探测逻辑os-prober扫描/bootos-proberefibootmgr双重探测UOS对Windows Boot Manager识别更激进易产生冲突启动项最典型的冲突场景一台预装Windows的国产笔记本安装UOS后os-prober会同时发现Windows Boot Manager和UOS的EFI条目update-grub默认将Windows排在第一位。用户重启后直接进Windows以为UOS没装上。解决方案不是删Windows项而是修改/etc/default/grubGRUB_DISABLE_OS_PROBERtrue # 然后手动在40_custom中添加UOS启动项5.2 GRUB模块加载机制为什么“minimal bash”下ls能用而lsmod不能GRUB启动时第一阶段代码boot.img从MBR或EFI分区加载第二阶段核心镜像core.img。core.img本身不包含全部功能而是按需加载模块.mod文件。/boot/grub/x86_64-efi/目录下的模块决定你能用什么命令。ls命令由part_msdos.mod、part_gpt.mod、ext2.mod等提供cat由cat.mod提供linux和initrd由linux.mod、multiboot.mod提供lsmod列出已加载模块由list.mod提供但该模块不在Deepin/UOS默认core.img中所以命令不存在。验证方法在GRUB命令行输入ls (hd0,gpt1)/boot/grub/x86_64-efi/ | grep list若无输出说明list.mod未被包含。这是Deepin/UOS为减小core.img体积做的裁剪——牺牲调试便利性换取更快的启动速度。5.3 UUID绑定机制为什么更换硬盘后GRUB仍能启动rootUUID...参数是GRUB稳定性的基石。UUID是文件系统级标识与设备名/dev/sda1无关。即使你把SSD从SATA口换到M.2口/dev/nvme0n1p1变成/dev/nvme1n1p1只要文件系统UUID不变GRUB就能正确定位。但有一个例外LVM逻辑卷。LVM的UUID是VG/LV层级的blkid显示的UUID属于LV而GRUB需要的是LV所在PV的UUID。因此LVM环境必须在/etc/default/grub中启用GRUB_PRELOAD_MODULESlvm否则grub-mkconfig无法解析LVM路径生成的grub.cfg中root参数会为空或错误。5.4 systemd-boot共存GRUB与systemd-boot的切换成本网络热词中出现grub systemd-boot说明部分用户尝试替换。但Deepin/UOS深度集成GRUB强行切换代价极高所有内核更新脚本/etc/kernel/postinst.d/针对GRUB编写deepin-control-center的启动项管理模块只读取grub.cfgUOS安全启动Secure Boot签名只适配shimx64.efigrubx64.efi链路。实测切换systemd-boot后apt upgrade会反复报错必须手动修改/etc/kernel/postinst.d/zz-update-grub脚本。除非你明确需要systemd-boot的特定特性如毫秒级启动否则不建议在生产环境替换。6. 实战案例复盘统信UOS V20在VMware Workstation中的引导故障全链路排查最后用一个真实案例收尾——它浓缩了前述所有知识点。某政务云项目客户要求在VMware Workstation 17上部署统信UOS V20但安装完成后始终卡在GRUB minimal bash。6.1 故障现象与初始判断VMware虚拟机配置2vCPU/4GB RAM/40GB SCSI硬盘固件类型为UEFIUOS安装ISOUOS_V20_Edition_2003.iso安装过程无报错分区方案为自动LVM首次重启后屏幕显示error: no such device: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. grub rescue第一反应是UUID错误但ls命令连分区都列不出来说明GRUB连硬盘都识别不了。6.2 排查链路从硬件模拟层到GRUB模块Step 1确认VMware存储控制器类型VMware默认SCSI控制器是LSI Logic SAS但UOS V20内核对它的驱动支持不完善。在VMware设置中将硬盘控制器改为NVMe需Workstation 17重启Live ISO重新安装。Step 2安装时禁用LVMLVM在VMware虚拟环境中易触发UUID解析延迟。安装界面选择“手动分区”创建/boot/efi512MB FAT32、/35GB ext4、swap4GB不启用LVM。Step 3安装后chroot修复GRUB安装完成不重启直接打开终端sudo su - mount /dev/sda2 /mnt mount /dev/sda1 /mnt/boot/efi mount --bind /dev /mnt/dev mount --bind /proc /mnt/proc mount --bind /sys /mnt/sys chroot /mnt在chroot中# 修复EFI启动项 grub-install --targetx86_64-efi --efi-directory/boot/efi --bootloader-iduniontech --recheck # 强制重新生成配置忽略os-prober grub-mkconfig -o /boot/grub/grub.cfg # 确认内核模块加载 ls /boot/grub/x86_64-efi/ | grep lvm # 应无输出因未用LVMStep 4验证EFI固件兼容性UOS V20的shimx64.efi签名在VMware UEFI中有时校验失败。若上述步骤后仍卡住需在VMware设置中关闭“安全启动”Security Boot再试。6.3 根本原因与长效方案最终定位VMware的LSI Logic SAS控制器在UEFI模式下其PCI设备ID未被UOS V20内核的mpt3sas驱动完全识别导致/dev/sda设备节点创建延迟。GRUB在超时时间内未能枚举到硬盘故报no such device。长效方案虚拟机模板统一使用NVMe控制器自动化部署脚本中加入GRUB修复步骤为VMware环境定制UOS内核启用CONFIG_SCSI_MPT3SASm模块化而非内置。这个案例告诉我们引导错误从来不只是GRUB的问题它是硬件抽象层、固件、内核驱动、引导加载器四层协作的最终体现。任何一个环节的微小偏差都会在GRUB这最后一道门上显现出来。我在客户现场用这套方法3小时内完成从故障定位到交付验证。他们后来把这份排查文档定为UOS虚拟化部署的标准SOP。真正的运维价值不在于多快重装系统而在于多准定位根因——而这正是深入理解GRUB底层逻辑带来的底气。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

八款降AI率工具实测:从检测原理到论文改写避坑指南 2026/10/2 10:35:35

八款降AI率工具实测:从检测原理到论文改写避坑指南

2026年专科生毕业季,我身边好几个朋友都在为同一件事头疼:论文写完了,AI检测那一关却过不去。学校的AIGC检测报告里,整段整段标红,AI疑似率直接飙到百分之七八十。我因为常年帮人改稿,最近一个月几乎把市面…

阅读更多 →
curl库64位bin选型与排错:从LNK1112到CMake完整落地 2026/10/2 10:35:35

curl库64位bin选型与排错:从LNK1112到CMake完整落地

简介:一个为六十四位Windows环境准备的CURL二进制库,由微软二零一七版工具编译,面向需要将网络请求集成到应用中的C/C开发者。CURL支持HTTP、HTTPS、FTP等协议,能完成文件上传下载、Cookie会话管理、请求重定向及多种身份认证&…

阅读更多 →
AI 辅助研发内部复盘(5/5):验证体系、产品协作与行动项(含硬核实战案例)——TaoToken 统一 Key 通道下的落地拆解 2026/10/2 10:35:35

AI 辅助研发内部复盘(5/5):验证体系、产品协作与行动项(含硬核实战案例)——TaoToken 统一 Key 通道下的落地拆解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
Unity线框渲染Shader实战:重心坐标法原理与移动端优化 2026/10/2 10:35:35

Unity线框渲染Shader实战:重心坐标法原理与移动端优化

1. 线框渲染到底解决什么问题 第一次接触线框渲染是在做一个工业设备的拆解演示项目。客户要求模型既能显示实体材质,又能随时切换到线框模式看内部结构,而且切换过程不能卡顿。当时我第一反应是用Unity自带的 GL.LINES 或者 MeshTopology.Lines 去画…

阅读更多 →
SpringBoot实战:古诗词鉴赏交流平台的设计与实现 2026/10/2 10:35:29

SpringBoot实战:古诗词鉴赏交流平台的设计与实现

1. 为什么我把毕业设计押在"古诗词鉴赏与交流平台"上1.1 选题的三个困境每年毕业设计选题的时候,我身边几乎都是同一批哀嚎:不想做那种烂大街的"学生管理系统",但算法方向又怕数学底子撑不住,前后端分离的电商…

阅读更多 →
SpringBoot+Vue旅游分享与商品交易平台:设计与实现全解析 2026/10/2 10:35:28

SpringBoot+Vue旅游分享与商品交易平台:设计与实现全解析

每年三四月份,我总会收到一大波“计算机毕业设计”相关的私信,其中“旅游信息交流网站”出现的频率特别高。大家觉得旅游类项目有画面感,页面好看,功能也能凑出来,但真正做起来才发现:分享、评论、点赞、下…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉