#新闻拍一拍# 美国国税局要求在纳税申报表上申报加密货币交易

Summary: • OpenSSH 8.4发布 为 FIDO/2FA 密钥带来更好的支持 • 英特尔异构编程器 oneAPI 发布 1.0 正式版

# #新闻拍一拍# 美国国税局要求在纳税申报表上申报加密货币交易

<img src="content_image/album_202009_29_112324df0oq7pqvfyfsaz8.jpg" alt="" title="">

#### 美国国税局要求在纳税申报表上申报加密货币交易

对加密货币态度改变和最近的其他行动表明，美国国税局正在认真对待加密货币对税收制度的威胁，无论对方是不合规的爱好者还是复杂的国际犯罪分子。许多加密货币用户对美国国税局的指导意见感到愤怒，该指南将比特币、以太币及其加密货币视为财产，而非货币。

来源：[cnbeta](https://www.cnbeta.com/articles/tech/1034783.htm)

>
> 拍一拍：加密货币已经从微不足道逐渐变成了正常经济生活的一部分。但是究竟该将加密货币视作货币还是资产，抑或兼而有之，这是一个全新的需要探索的领域。
>
>
>

#### OpenSSH 8.4发布 为 FIDO/2FA 密钥带来更好的支持

对于那些使用 YubiKey 或 Google Titan 安全密钥等 FIDO 密钥处理双因素认证的用户，OpenSSH 8.4 有了更好的支持。OpenSSH 8.4 现在支持每次使用都需要输入 PIN 码的 FIDO 密钥，SSHD 支持“验证-要求”选项，要求 FIDO 签名断言令牌被验证。

来源：[cnbeta](https://www.cnbeta.com/articles/tech/1034669.htm)

>
> 拍一拍：最好的安全就是“不用”密码的安全。
>
>
>

#### 英特尔异构编程器 oneAPI 发布 1.0 正式版

oneAPI 是英特尔推出的开源、基于标准的统一编程模型，旨在为从 CPU 到 GPU，再到 FPGA 等其他加速器的一系列硬件提供支持。oneAPI 的核心是英特尔的 Data Parallel C++（DPC++），是建立在 C++ 和 Khronos SYCL 标准之上的语言。除了基于 LLVM/Clang 的 DPC++ 编译器工具链外，oneAPI 还包含了许多库。

来源：[cnbeta](https://www.cnbeta.com/articles/tech/1034883.htm)

>
> 拍一拍：虽然不懂，但是感觉很厉害。
>
>
>


如何修复 Ubuntu Linux 中的 “Release file is not valid yet” 错误

Summary: 错误的原因是系统上的时间和现实世界的时间不同。

错误的原因是系统上的时间和现实世界的时间不同。


使用 RT-Thread 的 FinSH 对硬件进行编程

# 使用 RT-Thread 的 FinSH 对硬件进行编程

>
> 由于物联网（IoT）的兴起，对硬件进行编程变得越来越普遍。RT-Thread 可以让你可以用 FinSH 从 Linux 命令行与设备进行沟通、
>
>
>

RT-Thread 是一个开源的[实时操作系统](https://opensource.com/article/20/6/open-source-rtos)，用于对物联网（IoT）设备进行编程。FinSH 是 [RT-Thread](https://github.com/RT-Thread/rt-thread) 的命令行组件，它提供了一套操作界面，使用户可以从命令行与设备进行沟通。它主要用于调试或查看系统信息。

通常情况下，开发调试使用硬件调试器和 `printf` 日志来显示。但在某些情况下，这两种方法并不是很有用，因为它是从运行的内容中抽象出来的，而且它们可能很难解析。不过 RT-Thread 是一个多线程系统，当你想知道一个正在运行的线程的状态，或者手动控制系统的当前状态时，这很有帮助。因为它是多线程的，所以你能够拥有一个交互式的 shell，你可以直接在设备上输入命令、调用函数来获取你需要的信息，或者控制程序的行为。如果你只习惯于 Linux 或 BSD 等现代操作系统，这在你看来可能很普通，但对于硬件黑客来说，这是极其奢侈的，远超将串行电缆直接连线到电路板上以获取一丝错误的做法。

FinSH 有两种模式。

* C 语言解释器模式，称为 c-style。
* 传统的命令行模式，称为 msh（模块 shell）。

在 C 语言解释器模式下，FinSH 可以解析执行大部分 C 语言的表达式，并使用函数调用访问系统上的函数和全局变量。它还可以从命令行创建变量。

在 msh 模式下，FinSH 的操作与 Bash 等传统 shell 类似。

### GNU 命令标准

当我们在开发 FinSH 时，我们了解到，在编写命令行应用程序之前，你需要熟悉 GNU 命令行标准。这个标准实践的框架有助于给界面带入熟悉感，这有助于开发人员在使用时感到舒适和高效。

一个完整的 GNU 命令主要由四个部分组成。

1. 命令名（可执行文件）：命令行程序的名称；
2. 子命令：命令程序的子函数名称。
3. 选项：子命令函数的配置选项。
4. 参数：子命令函数配置选项的相应参数。

你可以在任何命令中看到这一点。以 Git 为例：

```
git reset --hard HEAD~1

```

这一点可以分解为：

可执行的命令是 `git`，子命令是 `reset`，使用的选项是 `--head`，参数是 `HEAD~1`。

再举个例子：

```
systemctl enable --now firewalld

```

可执行的命令是 `systemctl`，子命令是 `enable`，选项是 `--now`，参数是 `firewalld`。

想象一下，你想用 RT-Thread 编写一个符合 GNU 标准的命令行程序。FinSH 拥有你所需要的一切，并且会按照预期运行你的代码。更棒的是，你可以依靠这种合规性，让你可以自信地移植你最喜欢的 Linux 程序。

### 编写一个优雅的命令行程序

下面是一个 RT-Thread 运行命令的例子，RT-Thread 开发人员每天都在使用这个命令：

```
usage: env.py package [-h] [--force-update] [--update] [--list] [--wizard]
                      [--upgrade] [--printenv]

optional arguments:
  -h, --help      show this help message and exit
  --force-update  force update and clean packages, install or remove the
                  packages by your settings in menuconfig
  --update        update packages, install or remove the packages by your
                  settings in menuconfig
  --list          list target packages
  --wizard        create a new package with wizard
  --upgrade       upgrade local packages list and ENV scripts from git repo
  --printenv      print environmental variables to check

```

正如你所看到的那样，它看起来很熟悉，行为就像你可能已经在 Linux 或 BSD 上运行的大多数 POSIX 应用程序一样。当使用不正确或不充分的语法时，它会提供帮助，它支持长选项和短选项。这种通用的用户界面对于任何使用过 Unix 终端的人来说都是熟悉的。

### 选项种类

选项的种类很多，按长短可分为两大类。

1. 短选项：由一个连字符加一个字母组成，如 `pkgs -h` 中的 `-h` 选项。
2. 长选项：由两个连字符加上单词或字母组成，例如，`scons- --target-mdk5` 中的 `--target` 选项。

你可以把这些选项分为三类，由它们是否有参数来决定。

1. 没有参数：该选项后面不能有参数。
2. 参数必选：选项后面必须有参数。
3. 参数可选：选项后可以有参数，但不是必需的。

正如你对大多数 Linux 命令的期望，FinSH 的选项解析非常灵活。它可以根据空格或等号作为定界符来区分一个选项和一个参数，或者仅仅通过提取选项本身并假设后面的内容是参数（换句话说，完全没有定界符）。

* `wavplay -v 50`
* `wavplay -v50`
* `wavplay --vol=50`

### 使用 optparse

如果你曾经写过命令行程序，你可能会知道，一般来说，你所选择的语言有一个叫做 optparse 的库或模块。它是提供给程序员的，所以作为命令的一部分输入的选项（比如 `-v` 或 `--verbose`）可以与命令的其他部分进行*解析*。这可以帮助你的代码从一个子命令或参数中获取一个选项。

当为 FinSH 编写一个命令时，`optparse` 包希望使用这种格式：

```
MSH_CMD_EXPORT_ALIAS(pkgs, pkgs, this is test cmd.);

```

你可以使用长形式或短形式，或者同时使用两种形式来实现选项。例如：

```
static struct optparse_long long_opts[] =
{
    {"help"        , 'h', OPTPARSE_NONE}, // Long command: help, corresponding to short command h, without arguments.
    {"force-update",  0 , OPTPARSE_NONE}, // Long comman: force-update, without arguments
    {"update"      ,  0 , OPTPARSE_NONE},
    {"list"        ,  0 , OPTPARSE_NONE},
    {"wizard"      ,  0 , OPTPARSE_NONE},
    {"upgrade"     ,  0 , OPTPARSE_NONE},
    {"printenv"    ,  0 , OPTPARSE_NONE},
    { NULL         ,  0 , OPTPARSE_NONE}
};

```

创建完选项后，写出每个选项及其参数的命令和说明：

```
static void usage(void)
{
    rt_kprintf("usage: env.py package [-h] [--force-update] [--update] [--list] [--wizard]\n");
    rt_kprintf("                      [--upgrade] [--printenv]\n\n");
    rt_kprintf("optional arguments:\n");
    rt_kprintf("  -h, --help      show this help message and exit\n");
    rt_kprintf("  --force-update  force update and clean packages, install or remove the\n");
    rt_kprintf("                  packages by your settings in menuconfig\n");
    rt_kprintf("  --update        update packages, install or remove the packages by your\n");
    rt_kprintf("                  settings in menuconfig\n");
    rt_kprintf("  --list          list target packages\n");
    rt_kprintf("  --wizard        create a new package with wizard\n");
    rt_kprintf("  --upgrade       upgrade local packages list and ENV scripts from git repo\n");
    rt_kprintf("  --printenv      print environmental variables to check\n");
}

```

下一步是解析。虽然你还没有实现它的功能，但解析后的代码框架是一样的：

```
int pkgs(int argc, char **argv)
{
    int ch;
    int option_index;
    struct optparse options;

    if(argc == 1)
    {
        usage();
        return RT_EOK;
    }

    optparse_init(&options, argv);
    while((ch = optparse_long(&options, long_opts, &option_index)) != -1)
    {
        ch = ch;

        rt_kprintf("\n");
        rt_kprintf("optopt = %c\n", options.optopt);
        rt_kprintf("optarg = %s\n", options.optarg);
        rt_kprintf("optind = %d\n", options.optind);
        rt_kprintf("option_index = %d\n", option_index);
    }
    rt_kprintf("\n");

    return RT_EOK;
}

```

这里是函数头文件：

```
#include "optparse.h"
#include "finsh.h"

```

然后，编译并下载到设备上。

### 硬件黑客

对硬件进行编程似乎很吓人，但随着物联网的发展，它变得越来越普遍。并不是所有的东西都可以或者应该在树莓派上运行，但在 RT-Thread，FinSH 可以让你保持熟悉的 Linux 感觉。

如果你对在裸机上编码感到好奇，不妨试试 RT-Thread。

---

via: <https://opensource.com/article/20/9/hardware-command-line>

作者：[Alan Smithee](https://opensource.com/users/alansmithee) 选题：[lujun9972](https://github.com/lujun9972) 译者：[wxy](https://github.com/wxy) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


#新闻拍一拍# 部分勒索软件攻击时间只需不到 45 分钟

Summary: • CNNIC 发布《第 46 次中国互联网络发展状况统计报告》

• CNNIC 发布《第 46 次中国互联网络发展状况统计报告》

# #新闻拍一拍# 部分勒索软件攻击时间只需不到 45 分钟

<img src="content_image/album_202009_30_185944cf34ycxfszbmcmss.jpg" alt="" title="">

#### 部分勒索软件攻击时间只需不到 45 分钟

在微软发布的 88 页的《微软数字防御报告》中披露，勒索软件感染一直是该公司事件响应最常见的原因，最让微软头疼的是那些被称为“大型游戏猎人“和“人类操作的勒索软件”的团伙。今年，这些勒索软件团伙特别活跃，并且降低了他们发动攻击的时间。显然，由于 COVID-19 爆发，支付意愿会增加，在某些情况下，网络犯罪分子从最初入侵到赎回的时间在 45 分钟内完成。

来源：[zdnet](https://www.zdnet.com/article/microsoft-some-ransomware-attacks-take-less-than-45-minutes/)

>
> 拍一拍：产业化的勒索攻击正在大行其道，企业/政府需要有效的防御和拯救计划。
>
>
>

#### CNNIC 发布《第 46 次中国互联网络发展状况统计报告》

《报告》显示，截至 2020 年 6 月，我国网民规模达 9.40 亿，在线教育用户规模达 3.81 亿，在线医疗用户规模达 2.76 亿，远程办公用户规模达 1.99 亿。5G 终端连接数已超过 6600 万，已开通 5G 基站超 40 万个。我国已分配 IPv6 地址用户数达 14.42 亿，IPv6 活跃用户数达 3.62亿。网络视频（含短视频）用户规模达 8.88 亿，网络支付用户规模达 8.05 亿。

来源：[新浪科技](https://finance.sina.com.cn/tech/2020-09-29/doc-iivhvpwy9334481.shtml?qq-pf-to=pcqq.c2c)

>
> 拍一拍：这些数据表明，我们整个社会已经浸泡在互联网之中，这在 20 年前简直是不可想象的。
>
>
>


如何在 Linux 中创建/配置 LVM（逻辑卷管理）

Summary: 逻辑卷管理（LVM）在 Linux 系统中扮演着重要的角色，它可以提高可用性、磁盘 I/O、性能和磁盘管理的能力。

# 如何在 Linux 中创建/配置 LVM（逻辑卷管理）

<ruby> 逻辑卷管理 <rt>  Logical Volume Management </rt></ruby>（LVM）在 Linux 系统中扮演着重要的角色，它可以提高可用性、磁盘 I/O、性能和磁盘管理的能力。

LVM 是一种被广泛使用的技术，对于磁盘管理来说，它是非常灵活的。

它在物理磁盘和文件系统之间增加了一个额外的层，允许你创建一个逻辑卷而不是物理磁盘。

LVM 允许你在需要的时候轻松地调整、扩展和减少逻辑卷的大小。

### 如何创建 LVM 物理卷？

你可以使用任何磁盘、RAID 阵列、SAN 磁盘或分区作为 LVM <ruby> 物理卷 <rt>  Physical Volume </rt></ruby>（PV）。

让我们想象一下，你已经添加了三个磁盘，它们是 `/dev/sdb`、`/dev/sdc` 和 `/dev/sdd`。

运行以下命令来[发现 Linux 中新添加的 LUN 或磁盘](https://www.2daygeek.com/scan-detect-luns-scsi-disks-on-redhat-centos-oracle-linux/)：

```
# ls /sys/class/scsi_host
host0

```

```
# echo "- - -" > /sys/class/scsi_host/host0/scan

```

```
# fdisk -l

```

**创建物理卷 （`pvcreate`） 的一般语法：**

```
pvcreate [物理卷名]

```

当在系统中检测到磁盘，使用 `pvcreate` 命令初始化 LVM PV：

```
# pvcreate /dev/sdb /dev/sdc /dev/sdd
Physical volume "/dev/sdb" successfully created
Physical volume "/dev/sdc" successfully created
Physical volume "/dev/sdd" successfully created

```

**请注意：**

* 上面的命令将删除给定磁盘 `/dev/sdb`、`/dev/sdc` 和 `/dev/sdd` 上的所有数据。
* 物理磁盘可以直接添加到 LVM PV 中，而不必是磁盘分区。

使用 `pvdisplay` 和 `pvs` 命令来显示你创建的 PV。`pvs` 命令显示的是摘要输出，`pvdisplay` 显示的是 PV 的详细输出：

```
# pvs
PV VG Fmt Attr PSize PFree
/dev/sdb lvm2 a-- 15.00g 15.00g
/dev/sdc lvm2 a-- 15.00g 15.00g
/dev/sdd lvm2 a-- 15.00g 15.00g

```

```
# pvdisplay

"/dev/sdb" is a new physical volume of "15.00 GiB"
--- NEW Physical volume ---
PV Name               /dev/sdb
VG Name
PV Size               15.00 GiB
Allocatable           NO
PE Size               0
Total PE              0
Free PE               0
Allocated PE          0
PV UUID               69d9dd18-36be-4631-9ebb-78f05fe3217f

"/dev/sdc" is a new physical volume of "15.00 GiB"
--- NEW Physical volume ---
PV Name               /dev/sdc
VG Name
PV Size               15.00 GiB
Allocatable           NO
PE Size               0
Total PE              0
Free PE               0
Allocated PE          0
PV UUID               a2092b92-af29-4760-8e68-7a201922573b

"/dev/sdd" is a new physical volume of "15.00 GiB"
--- NEW Physical volume ---
PV Name               /dev/sdd
VG Name
PV Size               15.00 GiB
Allocatable           NO
PE Size               0
Total PE              0
Free PE               0
Allocated PE          0
PV UUID               d92fa769-e00f-4fd7-b6ed-ecf7224af7faS

```

### 如何创建一个卷组

<ruby> 卷组 <rt>  Volume Group </rt></ruby>（VG）是 LVM 结构中的另一层。基本上，卷组由你创建的 LVM 物理卷组成，你可以将物理卷添加到现有的卷组中，或者根据需要为物理卷创建新的卷组。

**创建卷组 （`vgcreate`） 的一般语法：**

```
vgcreate [卷组名] [物理卷名]

```

使用以下命令将一个新的物理卷添加到新的卷组中：

```
# vgcreate vg01 /dev/sdb /dev/sdc /dev/sdd
Volume group "vg01" successfully created

```

**请注意：**默认情况下，它使用 4MB 的<ruby> 物理范围 <rt>  Physical Extent </rt></ruby>（PE），但你可以根据你的需要改变它。

使用 `vgs` 和 `vgdisplay` 命令来显示你创建的 VG 的信息：

```
# vgs vg01
VG #PV #LV #SN Attr VSize VFree
vg01 3 0 0 wz--n- 44.99g 44.99g

```

```
# vgdisplay vg01
--- Volume group ---
VG Name              vg01
System ID
Format               lvm2
Metadata Areas       3
Metadata Sequence No 1
VG Access            read/write
VG Status            resizable
MAX LV               0
Cur LV               0
Open LV              0
Max PV               0
Cur PV               3
Act PV               3
VG Size              44.99 GiB
PE Size              4.00 MiB
Total PE             11511
Alloc PE / Size      0 / 0
Free PE / Size       11511 / 44.99 GiB
VG UUID              d17e3c31-e2c9-4f11-809c-94a549bc43b7

```

### 如何扩展卷组

如果 VG 没有空间，请使用以下命令将新的物理卷添加到现有卷组中。

**卷组扩展 （`vgextend`）的一般语法：**

```
vgextend [已有卷组名] [物理卷名]

```

```
# vgextend vg01 /dev/sde
    Volume group "vg01" successfully extended

```

### 如何以 GB 为单位创建逻辑卷？

<ruby> 逻辑卷 <rt>  Logical Volume </rt></ruby>（LV）是 LVM 结构中的顶层。逻辑卷是由卷组创建的块设备。它作为一个虚拟磁盘分区，可以使用 LVM 命令轻松管理。

你可以使用 `lvcreate` 命令创建一个新的逻辑卷。

**创建逻辑卷（`lvcreate`） 的一般语法：**

```
lvcreate –n [逻辑卷名] –L [逻辑卷大小] [要创建的 LV 所在的卷组名称]

```

运行下面的命令，创建一个大小为 10GB 的逻辑卷 `lv001`：

```
# lvcreate -n lv001 -L 10G vg01
Logical volume "lv001" created

```

使用 `lvs` 和 `lvdisplay` 命令来显示你所创建的 LV 的信息：

```
# lvs /dev/vg01/lvol01
LV VG Attr LSize Pool Origin Data% Move Log Cpy%Sync Convert
lv001 vg01 mwi-a-m-- 10.00g lv001_mlog 100.00

```

```
# lvdisplay /dev/vg01/lv001
--- Logical volume ---
LV Path                /dev/vg01/lv001
LV Name                lv001
VG Name                vg01
LV UUID                ca307aa4-0866-49b1-8184-004025789e63
LV Write Access        read/write
LV Creation host, time localhost.localdomain, 2020-09-10 11:43:05 -0700
LV Status              available
# open                 0
LV Size                10.00 GiB
Current LE             2560
Segments               1
Allocation             inherit
Read ahead sectors     auto
- currently set to     256
Block device           253:4

```

### 如何以 PE 大小创建逻辑卷？

或者，你可以使用物理范围（PE）大小创建逻辑卷。

### 如何计算 PE 值？

很简单，例如，如果你有一个 10GB 的卷组，那么 PE 大小是多少？

默认情况下，它使用 4MB 的物理范围，但可以通过运行 `vgdisplay` 命令来检查正确的 PE 大小，因为这可以根据需求进行更改。

```
10GB = 10240MB / 4MB （PE 大小） = 2560 PE

```

**用 PE 大小创建逻辑卷 （`lvcreate`） 的一般语法：**

```
lvcreate –n [逻辑卷名] –l [物理扩展 （PE） 大小] [要创建的 LV 所在的卷组名称]

```

要使用 PE 大小创建 10GB 的逻辑卷，命令如下：

```
# lvcreate -n lv001 -l 2560 vg01

```

### 如何创建文件系统

在创建有效的文件系统之前，你不能使用逻辑卷。

**创建文件系统的一般语法：**

```
mkfs –t [文件系统类型] /dev/[LV 所在的卷组名称]/[LV 名称]

```

使用以下命令将逻辑卷 `lv001` 格式化为 ext4 文件系统：

```
# mkfs -t ext4 /dev/vg01/lv001

```

对于 xfs 文件系统：

```
# mkfs -t xfs /dev/vg01/lv001

```

### 挂载逻辑卷

最后，你需要挂载逻辑卷来使用它。确保在 `/etc/fstab` 中添加一个条目，以便系统启动时自动加载。

创建一个目录来挂载逻辑卷：

```
# mkdir /lvmtest

```

使用挂载命令[挂载逻辑卷](https://www.2daygeek.com/mount-unmount-file-system-partition-in-linux/)：

```
# mount /dev/vg01/lv001 /lvmtest

```

在 [/etc/fstab 文件](https://www.2daygeek.com/understanding-linux-etc-fstab-file/)中添加新的逻辑卷详细信息，以便系统启动时自动挂载：

```
# vi /etc/fstab
/dev/vg01/lv001 /lvmtest xfs defaults 0 0

```

使用 [df 命令](https://www.2daygeek.com/linux-check-disk-space-usage-df-command/)检查新挂载的卷：

```
# df -h /lvmtest
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/vg01-lv001 15360M 34M 15326M 4% /lvmtest

```

---

via: <https://www.2daygeek.com/create-lvm-storage-logical-volume-manager-in-linux/>

作者：[Magesh Maruthamuthu](https://www.2daygeek.com/author/magesh/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[geekpi](https://github.com/geekpi) 校对：[校对者ID](https://github.com/%E6%A0%A1%E5%AF%B9%E8%80%85ID)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


10 大静态网站生成工具

这个不错，收藏了

# 10 大静态网站生成工具

>
> 在寻找部署静态网页的方法吗？这几个开源的静态网站生成工具可以帮你迅速部署界面优美、功能强大的静态网站，无需掌握复杂的 HTML 和 CSS 技能。
>
>
>

### 静态网站是什么？

技术上来讲，静态网站是指网页不是由服务器动态生成的。HTML、CSS 和 JavaScript 文件就静静地躺在服务器的某个路径下，它们的内容与终端用户接收到的版本是一样的。原始的源码文件已经提前编译好了，源码在每次请求后都不会变化。

[Linux.CN](http://Linux.CN) 是一个依赖多个数据库的动态网站，当有浏览器的请求时，网页就会生成并提供服务。大部分网站是动态的，你与这些网站互动时，大量的内容会经常改变。

静态网站有一些好处，比如加载时间更短，请求的服务器资源更少、更安全（值得商榷）。

传统上，静态网站更适合于创建只有少量网页、内容变化不频繁的小网站。

然而，随着静态网站生成工具出现后，静态网站的适用范围越来越大。你还可以使用这些工具搭建博客网站。

我整理了几个开源的静态网站生成工具，这些工具可以帮你搭建界面优美的网站。

### 最好的开源静态网站生成工具

请注意，静态网站不会提供很复杂的功能。如果你需要复杂的功能，那么你可以参考适用于动态网站的[最佳开源 CMS](https://itsfoss.com/open-source-cms/)列表。

#### 1、Jekyll

Jekyll 是用 [Ruby](https://www.ruby-lang.org/en/) 写的最受欢迎的开源静态生成工具之一。实际上，Jekyll 是 [GitHub 页面](https://pages.github.com/) 的引擎，它可以让你免费用 GitHub 托管网站。

你可以很轻松地跨平台配置 Jekyll，包括 Ubuntu。它利用 [Markdown](https://github.com/Shopify/liquid/wiki)、[Liquid](https://github.com/Shopify/liquid/wiki)（模板语言）、HTML 和 CSS 来生成静态的网页文件。如果你要搭建一个没有广告或推广自己工具或服务的产品页的博客网站，它是个不错的选择。

它还支持从常见的 CMS（<ruby> 内容管理系统 <rt>  Content management system </rt></ruby>）如 Ghost、WordPress、Drupal 7 迁移你的博客。你可以管理永久链接、类别、页面、文章，还可以自定义布局，这些功能都很强大。因此，即使你已经有了一个网站，如果你想转成静态网站，Jekyll 会是一个完美的解决方案。你可以参考[官方文档](https://jekyllrb.com/docs/)或 [GitHub 页面](https://github.com/jekyll/jekyll)了解更多内容。

* [Jekyll](https://jekyllrb.com/)

#### 2、Hugo

Hugo 是另一个很受欢迎的用于搭建静态网站的开源框架。它是用 [Go 语言](https://golang.org/)写的。

它运行速度快、使用简单、可靠性高。如果你需要，它也可以提供更高级的主题。它还提供了一些有用的快捷方式来帮助你轻松完成任务。无论是组合展示网站还是博客网站，Hogo 都有能力管理大量的内容类型。

如果你想使用 Hugo，你可以参照它的[官方文档](https://gohugo.io/getting-started/)或它的 [GitHub 页面](https://github.com/gohugoio/hugo)来安装以及了解更多相关的使用方法。如果需要的话，你还可以将 Hugo 部署在 GitHub 页面或任何 CDN 上。

* [Hugo](https://gohugo.io/)

#### 3、Hexo

Hexo 是一个有趣的开源框架，基于 [Node.js](https://nodejs.org/en/)。像其他的工具一样，你可以用它搭建相当快速的网站，不仅如此，它还提供了丰富的主题和插件。

它还根据用户的每个需求提供了强大的 API 来扩展功能。如果你已经有一个网站，你可以用它的[迁移](https://hexo.io/api/migrator.html)扩展轻松完成迁移工作。

你可以参照[官方文档](https://hexo.io/docs/)或 [GitHub 页面](https://github.com/hexojs/hexo) 来使用 Hexo。

* [Hexo](https://hexo.io/)

#### 4、Gatsby

Gatsby 是一个越来越流行的开源网站生成框架。它使用 [React.js](https://reactjs.org/) 来生成快速、界面优美的网站。

几年前在一个实验性的项目中，我曾经非常想尝试一下这个工具，它提供的成千上万的新插件和主题的能力让我印象深刻。与其他静态网站生成工具不同的是，你可以使用 Gatsby 生成一个网站，并在不损失任何功能的情况下获得静态网站的好处。

它提供了与很多流行的服务的整合功能。当然，你可以不使用它的复杂的功能，或将其与你选择的流行 CMS 配合使用，这也会很有趣。你可以查看他们的[官方文档](https://www.gatsbyjs.com/docs/)或它的 [GitHub 页面](https://github.com/gatsbyjs/gatsby)了解更多内容。

* [Gatsby](https://www.gatsbyjs.com/)

#### 5、VuePress

VuePress 是由 [Vue.js](https://vuejs.org/) 支持的静态网站生成工具，而 Vue.js 是一个开源的渐进式 JavaScript 框架。

如果你了解 HTML、CSS 和 JavaScript，那么你可以无压力地使用 VuePress。你应该可以找到几个有用的插件和主题来为你的网站建设开个头。此外，看起来 Vue.js 的更新一直很活跃，很多开发者都在关注 Vue.js，这是一件好事。

你可以参照他们的[官方文档](https://vuepress.vuejs.org/guide/)和 [GitHub 页面](https://github.com/vuejs/vuepress)了解更多。

* [VuePress](https://vuepress.vuejs.org/)

#### 6、Nuxt.js

Nuxt.js 使用了 Vue.js 和 Node.js，但它致力于模块化，并且有能力依赖服务端而非客户端。不仅如此，它的目标是为开发者提供直观的体验，并提供描述性错误，以及详细的文档等。

正如它声称的那样，在你用来搭建静态网站的所有工具中，Nuxt.js 可以做到功能和灵活性两全其美。他们还提供了一个 [Nuxt 线上沙盒](https://template.nuxtjs.org/)，让你不费吹灰之力就能直接测试它。

你可以查看它的 [GitHub 页面](https://github.com/nuxt/nuxt.js)和[官方网站](https://nuxtjs.org/)了解更多。

* [Nuxt.js](https://nuxtjs.org/)

#### 7、Docusaurus

Docusaurus 是一个有趣的开源静态网站生成工具，为搭建文档类网站量身定制。它还是 [Facebook 开源计划](https://opensource.facebook.com/)的一个项目。

Docusaurus 是用 React 构建的。你可以使用所有的基本功能，像文档版本管理、文档搜索和翻译大多是预先配置的。如果你想为你的产品或服务搭建一个文档网站，那么可以试试 Docusaurus。

你可以从它的 [GitHub 页面](https://github.com/facebook/docusaurus)和它的[官网](https://docusaurus.io/)获取更多信息。

* [Docusaurus](https://docusaurus.io/)

#### 8、Eleventy

Eleventy 自称是 Jekyll 的替代品，旨在以更简单的方法来制作更快的静态网站。

它似乎很容易上手，而且它还提供了适当的文档来帮助你。如果你想找一个简单的静态网站生成工具，Eleventy 似乎会是一个有趣的选择。

你可以参照它的 [GitHub 页面](https://github.com/11ty/eleventy/)和[官网](https://www.11ty.dev/)来了解更多的细节。

* [Eleventy](https://www.11ty.dev/)

#### 9、Publii

Publii 是一个令人印象深刻的开源 CMS，它能使生成一个静态网站变得很容易。它是用 [Electron](https://www.electronjs.org) 和 Vue.js 构建的。如果有需要，你也可以把你的文章从 WorkPress 网站迁移过来。此外，它还提供了与 GitHub 页面、Netlify 及其它类似服务的一键同步功能。

如果你利用 Publii 生成一个静态网站，你还可以得到一个所见即所得的编辑器。你可以从[官网](https://getpublii.com/)下载它，或者从它的 [GitHub 页面](https://github.com/GetPublii/Publii)了解更多信息。

* [Publii](https://getpublii.com/)

#### 10、Primo

一个有趣的开源静态网站生成工具，目前开发工作仍很活跃。虽然与其他的静态生成工具相比，它还不是一个成熟的解决方案，有些功能还不完善，但它是一个独特的项目。

Primo 旨在使用可视化的构建器帮你构建和搭建网站，这样你就可以轻松编辑和部署到任意主机上。

你可以参照[官网](https://primo.af/)或查看它的 [GitHub 页面](https://github.com/primo-app/primo-desktop)了解更多信息。

* [Primo](https://primo.af/)

### 结语

还有很多文章中没有列出的网站生成工具。然而，我试图提到最好的静态生成器，为您提供最快的加载时间，最好的安全性和令人印象深刻的灵活性。

列表中没有你最喜欢的工具？在下面的评论中告诉我。

---

via: <https://itsfoss.com/open-source-static-site-generators/>

作者：[Ankush Das](https://itsfoss.com/author/ankush/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[lxbwolf](https://github.com/lxbwolf) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


#新闻拍一拍# 泄露的 Windows XP 代码经用户编译为可运行的系统

Summary: • Khronos 开源新 OpenCL SDK，并发布 OpenCL 3.0 规范 • 开发者再次尝试让摩托罗拉 68000 系列进入 LLVM 上游

# #新闻拍一拍# 泄露的 Windows XP 代码经用户编译为可运行的系统

<img src="content_image/album_202010_01_133351fzc4pcv8rzkn84vb.jpg" alt="" title="">

#### 泄露的 Windows XP 代码经用户编译为可运行的系统

上周在 4chan 网上泄露的 Windows XP 和 Windows Server 2003 源代码，在一位用户将代码编译成可运行的操作系统后，已被证实是真实的。NTDEV 是上周下载代码的数百万用户之一，他决定编译代码，自己去判断代码真假。根据网上分享的视频，这位业余 IT 技术人员在周末成功编译了 Windows XP 代码，昨天又成功编译了 Windows Server 2003。

来源：[zdnet](https://www.zdnet.com/article/windows-xp-leak-confirmed-after-user-compiles-the-leaked-code-into-a-working-os/)

>
> 拍一拍：这种实锤方法令人惊叹！
>
>
>

#### Khronos 开源新 OpenCL SDK，并发布 OpenCL 3.0 规范

OpenCL 3.0 主要变化还是让 OpenCL 2 的功能成为可选，所以 OpenCL 3.0 适合更多的设备和环境，但也增加了新的扩展，围绕着能够在给定的 OpenCL 驱动/设备上获得 UUID，还增加了异步 DMA 功能，有助于 OpenCL 3.0 对嵌入式处理器的工作。

来源：[cnbeta](https://www.cnbeta.com/articles/tech/1035659.htm)

>
> 拍一拍：以防你不知道，OpenCL 是第一个面向异构系统通用目的并行编程的开放式、免费标准，也是一个统一的编程环境。
>
>
>

#### 开发者再次尝试让摩托罗拉 68000 系列进入 LLVM 上游

摩托罗拉 68000 系列（M68k）处理器在 80 年代就已经出现了，这要归功于早期苹果 Macintosh 电脑。到 2020 年，M68k 仍然是复古电脑爱好者和业余爱好者的热门目标。这已经不是第一次提出对 LLVM 的 M68k 支持了。

来源：[phoronix](https://www.phoronix.com/scan.php?page=news_item&px=LLVM-Motorola-6800-Series-2020)

>
> 拍一拍：这是一种计算机考古需要。
>
>
>


如何在 Linux 中扩展/增加 LVM 大小（逻辑卷调整）

Summary: 扩展逻辑卷非常简单，只需要很少的步骤，而且不需要卸载某个逻辑卷就可以在线完成。

我觉得文章没有很好地区分卷组和卷的概念，新手初看有点迷糊。<br />
我现在觉得vg代表卷组，lv代表卷

扩展逻辑卷非常简单，只需要很少的步骤，而且不需要卸载某个逻辑卷就可以在线完成。

# 如何在 Linux 中扩展/增加 LVM 大小（逻辑卷调整）

扩展逻辑卷非常简单，只需要很少的步骤，而且不需要卸载某个逻辑卷就可以在线完成。

LVM 的主要目的是灵活的磁盘管理，当你需要的时候，可以很方便地调整、扩展和缩小逻辑卷的大小。

如果你是逻辑卷管理（LVM） 新手，我建议你从我们之前的文章开始学习。

* **第一部分：[如何在 Linux 中创建/配置 LVM（逻辑卷管理）](/article-12670-1.html)**

扩展逻辑卷涉及到以下步骤：

* 检查逻辑卷（LV）所在的卷组中是否有足够的未分配磁盘空间
* 如果有，你可以使用这些空间来扩展逻辑卷
* 如果没有，请向系统中添加新的磁盘或 LUN
* 将物理磁盘转换为物理卷（PV）
* 扩展卷组
* 增加逻辑卷大小
* 扩大文件系统
* 检查扩展的文件系统大小

### 如何创建 LVM 物理卷？

使用 `pvcreate` 命令创建 LVM 物理卷。

当在操作系统中检测到磁盘，使用 `pvcreate` 命令初始化 LVM 物理卷：

```
# pvcreate /dev/sdc
Physical volume "/dev/sdc" successfully created

```

**请注意：**

* 上面的命令将删除磁盘 `/dev/sdc` 上的所有数据。
* 物理磁盘可以直接添加到 LVM 物理卷中，而不是磁盘分区。

使用 `pvdisplay` 命令来显示你所创建的物理卷：

```
# pvdisplay /dev/sdc

"/dev/sdc" is a new physical volume of "10.00 GiB"
--- NEW Physical volume ---
PV Name               /dev/sdc
VG Name
PV Size               10.00 GiB
Allocatable           NO
PE Size               0
Total PE              0
Free PE               0
Allocated PE          0
PV UUID               69d9dd18-36be-4631-9ebb-78f05fe3217f

```

### 如何扩展卷组

使用以下命令在现有的卷组（VG）中添加一个新的物理卷：

```
# vgextend vg01 /dev/sdc
Volume group "vg01" successfully extended

```

使用 `vgdisplay` 命令来显示你所创建的物理卷：

```
# vgdisplay vg01
--- Volume group ---
VG Name              vg01
System ID
Format               lvm2
Metadata Areas       2
Metadata Sequence No 1
VG Access            read/write
VG Status            resizable
MAX LV               0
Cur LV               0
Open LV              0
Max PV               0
Cur PV               2
Act PV               2
VG Size              14.99 GiB
PE Size              4.00 MiB
Total PE             3840
Alloc PE / Size      1280 / 4.99
Free PE / Size       2560 / 9.99 GiB
VG UUID              d17e3c31-e2c9-4f11-809c-94a549bc43b7

```

### 如何扩展逻辑卷？

使用以下命令增加现有逻辑卷大小。

**逻辑卷扩展（`lvextend`）的常用语法：**

```
lvextend [要增加的额外空间] [现有逻辑卷名称]

```

使用下面的命令将现有的逻辑卷增加 10GB：

```
# lvextend -L +10G /dev/mapper/vg01-lv002
Rounding size to boundary between physical extents: 5.90 GiB
Size of logical volume vg01/lv002 changed from 5.00 GiB (1280 extents) to 15.00 GiB (3840 extents).
Logical volume var successfully resized

```

使用 PE 大小来扩展逻辑卷：

```
# lvextend -l +2560 /dev/mapper/vg01-lv002

```

要使用百分比（%）扩展逻辑卷，请使用以下命令：

```
# lvextend -l +40%FREE /dev/mapper/vg01-lv002

```

现在，逻辑卷已经扩展，你需要调整文件系统的大小以扩展逻辑卷内的空间：

对于基于 ext3 和 ext4 的文件系统，运行以下命令：

```
# resize2fs /dev/mapper/vg01-lv002

```

对于 xfs 文件系统，使用以下命令：

```
# xfs_growfs /dev/mapper/vg01-lv002

```

使用 [df 命令](https://www.2daygeek.com/linux-check-disk-space-usage-df-command/)查看文件系统大小：

```
# df -h /lvmtest1
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/vg01-lv002 15360M 34M 15326M 4% /lvmtest1

```

---

via: <https://www.2daygeek.com/extend-increase-resize-lvm-logical-volume-in-linux/>

作者：[Magesh Maruthamuthu](https://www.2daygeek.com/author/magesh/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


用 testdisk 恢复 Linux 上已删除的文件

# 用 testdisk 恢复 Linux 上已删除的文件

>
> 这篇文章介绍了 testdisk，这是恢复最近删除的文件（以及用其他方式修复分区）的工具之一，非常方便。
>
>
>

<img src="content_image/album_202010_02_003801ijb4e4lqrzqeanjz.jpg" alt="" title="">

当你在 Linux 系统上删除一个文件时，它不一定会永远消失，特别是当你最近才刚刚删除了它的时候。

除非你用 `shred` 等工具把它擦掉，否则数据仍然会放在你的磁盘上 —— 而恢复已删除文件的最佳工具之一 `testdisk` 可以帮助你拯救它。虽然 `testdisk` 具有广泛的功能，包括恢复丢失或损坏的分区和使不能启动磁盘可以重新启动，但它也经常被用来恢复被误删的文件。

在本篇文章中，我们就来看看如何使用 `testdisk` 恢复已删除的文件，以及该过程中的每一步是怎样的。由于这个过程需要不少的步骤，所以当你做了几次之后，你可能会觉得操作起来会更加得心应手。

### 安装 testdisk

可以使用 `apt install testdisk` 或 `yum install testdisk` 等命令安装 `testdisk`。有趣的是，它不仅是一个 Linux 工具，而且还适用于 MacOS、Solaris 和 Windows。

文档可在 [cgsecurity.org](https://www.cgsecurity.org/testdisk.pdf) 中找到。

### 恢复文件

首先，你必须以 `root` 身份登录，或者有 `sudo` 权限才能使用 `testdisk`。如果你没有 `sudo` 访问权限，你会在这个过程一开始就被踢出，而如果你选择创建了一个日志文件的话，最终会有这样的消息：

```
TestDisk exited normally.
jdoe is not in the sudoers file.  This incident will be reported.

```

当你用 `testdisk` 恢复被删除的文件时，你最终会将恢复的文件放在你启动该工具的目录下，而这些文件会属于 `root`。出于这个原因，我喜欢在 `/home/recovery` 这样的目录下启动。一旦文件被成功地还原和验证，就可以将它们移回它们的所属位置，并将它们的所有权也恢复。

在你可以写入的选定目录下开始：

```
$ cd /home/recovery
$ testdisk

```

`testdisk` 提供的第一页信息描述了该工具并显示了一些选项。至少在刚开始，创建个日志文件是个好主意，因为它提供的信息可能会被证明是有用的。下面是如何做的：

```
Use arrow keys to select, then press Enter key:
>[ Create ] Create a new log file
 [ Append ] Append information to log file
 [ No Log ] Don’t record anything

```

左边的 `>` 以及你看到的反转的字体和背景颜色指出了你按下回车键后将使用的选项。在这个例子中，我们选择了创建日志文件。

然后会提示你输入密码（除非你最近使用过 `sudo`）。

下一步是选择被删除文件所存储的磁盘分区（如果没有高亮显示的话）。根据需要使用上下箭头移动到它。然后点两次右箭头，当 “Proceed” 高亮显示时按回车键。

```
Select a media (use Arrow keys, then press Enter):
 Disk /dev/sda - 120 GB / 111 GiB - SSD2SC120G1CS1754D117-551
>Disk /dev/sdb - 500 GB / 465 GiB - SAMSUNG HE502HJ
 Disk /dev/loop0 - 13 MB / 13 MiB (RO)
 Disk /dev/loop1 - 101 MB / 96 MiB (RO)
 Disk /dev/loop10 - 148 MB / 141 MiB (RO)
 Disk /dev/loop11 - 36 MB / 35 MiB (RO)
 Disk /dev/loop12 - 52 MB / 49 MiB (RO)
 Disk /dev/loop13 - 78 MB / 75 MiB (RO)
 Disk /dev/loop14 - 173 MB / 165 MiB (RO)
 Disk /dev/loop15 - 169 MB / 161 MiB (RO)
>[Previous]  [  Next  ]  [Proceed ]  [  Quit  ]

```

在这个例子中，被删除的文件在 `/dev/sdb` 的主目录下。

此时，`testdisk` 应该已经选择了合适的分区类型。

```
Disk /dev/sdb - 500 GB / 465 GiB - SAMSUNG HE502HJ

Please select the partition table type, press Enter when done.
 [Intel  ] Intel/PC partition
>[EFI GPT] EFI GPT partition map (Mac i386, some x86_64...)
 [Humax  ] Humax partition table
 [Mac    ] Apple partition map (legacy)
 [None   ] Non partitioned media
 [Sun    ] Sun Solaris partition
 [XBox   ] XBox partition
 [Return ] Return to disk selection

```

在下一步中，按向下箭头指向 “[ Advanced ] Filesystem Utils”。

```
[ Analyse  ] Analyse current partition structure and search for lost partitions
>[ Advanced ] Filesystem Utils
 [ Geometry ] Change disk geometry
 [ Options  ] Modify options
 [ Quit     ] Return to disk selection

```

接下来，查看选定的分区。

```
Partition                  Start        End    Size in sectors
> 1 P Linux filesys. data         2048  910155775  910153728 [drive2]

```

然后按右箭头选择底部的 “[ List ]”，按回车键。

```
[  Type  ]  [Superblock] >[  List  ]  [Image Creation]  [  Quit  ]

```

请注意，它看起来就像我们从根目录 `/` 开始，但实际上这是我们正在工作的文件系统的基点。在这个例子中，就是 `/home`。

```
Directory /   <== 开始点

>drwxr-xr-x     0     0      4096 23-Sep-2020 17:46 .
 drwxr-xr-x     0     0      4096 23-Sep-2020 17:46 ..
 drwx———     0     0     16384 22-Sep-2020 11:30 lost+found
 drwxr-xr-x  1008  1008      4096  9-Jul-2019 14:10 dorothy
 drwxr-xr-x  1001  1001      4096 22-Sep-2020 12:12 nemo
 drwxr-xr-x  1005  1005      4096 19-Jan-2020 11:49 eel
 drwxrwxrwx     0     0      4096 25-Sep-2020 08:08 recovery
...

```

接下来，我们按箭头指向具体的主目录。

```
drwxr-xr-x  1016  1016      4096 17-Feb-2020 16:40 gino
>drwxr-xr-x  1000  1000     20480 25-Sep-2020 08:00 shs

```

按回车键移动到该目录，然后根据需要向下箭头移动到子目录。注意，如果选错了，可以选择列表顶部附近的 `..` 返回。

如果找不到文件，可以按 `/`（就像在 `vi` 中开始搜索时一样），提示你输入文件名或其中的一部分。

```
Directory /shs     <== current location
                                                   Previous
...
 -rw-rw-r—  1000  1000       426  8-Apr-2019 19:09 2-min-topics
>-rw-rw-r—  1000  1000     24667  8-Feb-2019 08:57 Up_on_the_Roof.pdf

```

一旦你找到需要恢复的文件，按 `c` 选择它。

注意：你会在屏幕底部看到有用的说明：

```
Use Left arrow to go back, Right to change directory, h to hide deleted files
    q to quit, : to select the current file, a to select all files
    C to copy the selected files, c to copy the current file <==

```

这时，你就可以在起始目录内选择恢复该文件的位置了（参见前面的说明，在将文件移回原点之前，先在一个合适的地方进行检查）。在这种情况下，`/home/recovery` 目录没有子目录，所以这就是我们的恢复点。

注意：你会在屏幕底部看到有用的说明：

```
Please select a destination where /shs/Up_on_the_Roof.pdf will be copied.
Keys: Arrow keys to select another directory
      C when the destination is correct
      Q to quit
Directory /home/recovery    <== 恢复位置

```

一旦你看到 “Copy done! 1 ok, 0 failed” 的绿色字样，你就会知道文件已经恢复了。

在这种情况下，文件被留在 `/home/recovery/shs` 下（起始目录，附加所选目录）。

在将文件移回原来的位置之前，你可能应该先验证恢复的文件看起来是否正确。确保你也恢复了原来的所有者和组，因为此时文件由 root 拥有。

**注意：** 对于文件恢复过程中的很多步骤，你可以使用退出（按 `q` 或“[ Quit ]”）来返回上一步。如果你愿意，可以选择退出选项一直回到该过程中的第一步，也可以选择按下 `^c` 立即退出。

#### 恢复训练

使用 `testdisk` 恢复文件相对来说没有痛苦，但有些复杂。在恐慌时间到来之前，最好先练习一下恢复文件，让自己有机会熟悉这个过程。

---

via: <https://www.networkworld.com/article/3575524/recovering-deleted-files-on-linux-with-testdisk.html>

作者：[Sandra Henry-Stocker](https://www.networkworld.com/author/Sandra-Henry_Stocker/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[wxy](https://github.com/wxy) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


#新闻拍一拍# 美财政部：向勒索软件交付赎金可能被罚

Summary: • Outlook 服务瘫痪故障开始影响其全球用户 • IBM 将开源太空垃圾防撞系统

# #新闻拍一拍# 美财政部：向勒索软件交付赎金可能被罚

<img src="content_image/album_202010_02_062130njeoj3cc8cgk9cz1.jpg" alt="" title="">

#### 美财政部：向勒索软件交付赎金可能被罚

美国财政部警告说，如果从攻击中获利的勒索者已经受到了美国经济制裁，那么受到勒索软件伤害的公司和帮忙与勒索软件勒索者谈判的公司可能面临美国联邦政府的巨额罚款。美国财政部对一些人和机构实施了经济制裁，有效冻结了这些人（受美国管辖）的所有财产和利益，并将与他们交易定为犯罪。这包括朝鲜、伊朗、俄罗斯等一些人或机构。而向这些被制裁对象缴纳赎金可能面临违反 OFAC（外国资产控制办公室）法规的风险。

来源：[krebsonsecurity](https://krebsonsecurity.com/2020/10/ransomware-victims-that-pay-up-could-incur-steep-fines-from-uncle-sam/)

>
> 拍一拍：这就面临更糟糕的局面，有可能遭受勒索后，你只能被“撕票”。
>
>
>

#### Outlook 服务瘫痪故障开始影响其全球用户

在印度之后，世界各地的 Outlook 在线用户都报告了访问微软服务的问题。微软最初表示，印度的用户是受影响的主要群体。不过，该公司随后在微软 365 状态推特账号上证实，该问题正在影响全球用户。微软正在调查最近对其服务的更新，以确定问题的潜在来源。然而，没有迹象表明何时会解决这个问题。

来源：[cnbeta](https://www.cnbeta.com/articles/tech/1035967.htm)

>
> 拍一拍：大规模云技术服务如火如荼的发展的背后，就是可能会造成大规模的服务故障。
>
>
>

#### IBM 将开源太空垃圾防撞系统

太空已经是一个相当混乱的地方，有成千上万的人造物体以每秒 8000 米的速度飞行在地球上空飞来飞去。这使得在发射前很久就能预测人造物体的轨迹并做出相应的计划变得极为重要。IBM 和德克萨斯大学 Moriba Jah 博士的一个空间态势感知（SSA）开源项目，目的是确定和预测这些太空垃圾的位置。这一项目背后的想法是，当物理模型错误地预测未来位置时，机器学习可以创建学习模型。

来源：[zdnet](https://www.zdnet.com/article/ibm-to-open-source-space-junk-collision-avoidance/)

>
> 拍一拍：AI 应用到这个这个领域是很有意义的。
>
>
>


使用 qemu-kvm 安装和运行 Vagrant

# 使用 qemu-kvm 安装和运行 Vagrant

<img src="content_image/album_202010_03_094655p9j204bt8em0plje.png" alt="" title="">

Vagrant 是一个出色的工具，DevOps 专业人员、程序员、系统管理员和普通极客来使用它来建立可重复的基础架构来进行开发和测试。引用自它的网站：

>
> Vagrant 是用于在单工作流程中构建和管理虚拟机环境的工具。凭借简单易用的工作流程并专注于自动化，Vagrant 降低了开发环境的设置时间，提高了生产效率，并使“在我的机器上可以工作”的借口成为过去。
>
>
> 如果你已经熟悉 Vagrant 的基础知识，那么该文档为所有的功能和内部结构提供了更好的参考。
>
>
> Vagrant 提供了基于行业标准技术构建的、易于配置、可复制、可移植的工作环境，并由一个一致的工作流程控制，帮助你和你的团队最大限度地提高生产力和灵活性。
>
>
> <https://www.vagrantup.com/intro>
>
>
>

本指南将逐步介绍使 Vagrant 在基于 Fedora 的计算机上工作所需的步骤。

我从最小化安装 Fedora 服务器开始，因为这样可以减少宿主机操作系统的内存占用，但如果你已经有一台可以使用的 Fedora 机器，无论是服务器还是工作站版本，那么也没问题。

### 检查机器是否支持虚拟化

```
$ sudo lscpu | grep Virtualization

```

```
Virtualization:                  VT-x
Virtualization type:             full

```

### 安装 qemu-kvm

```
sudo dnf install qemu-kvm libvirt libguestfs-tools virt-install rsync

```

### 启用并启动 libvirt 守护进程

```
sudo systemctl enable --now libvirtd

```

### 安装 Vagrant

```
sudo dnf install vagrant

```

### 安装 Vagrant libvirtd 插件

```
sudo vagrant plugin install vagrant-libvirt

```

### 添加一个 box

```
vagrant box add fedora/32-cloud-base --provider=libvirt

```

（LCTT 译注：以防你不知道，box 是 Vagrant 中的一种包格式，Vagrant 支持的任何平台上的任何人都可以使用盒子来建立相同的工作环境。）

### 创建一个最小化的 Vagrantfile 来测试

```
$ mkdir vagrant-test
$ cd vagrant-test
$ vi Vagrantfile

```

```
Vagrant.configure("2") do |config|
  config.vm.box = "fedora/32-cloud-base"
end

```

**注意文件名和文件内容的大小写。**

### 检查文件

```
vagrant status

```

```
Current machine states:

default not created (libvirt)

The Libvirt domain is not created. Run 'vagrant up' to create it.

```

### 启动 box

```
vagrant up

```

### 连接到你的新机器

```
vagrant ssh

```

完成了。现在你的 Fedora 机器上 Vagrant 可以工作了。

要停止该机器，请使用 `vagrant halt`。这只是简单地停止机器，但保留虚拟机和磁盘。 要关闭并删除它，请使用 `vagrant destroy`。这将删除整个机器和你在其中所做的任何更改。

### 接下来的步骤

在运行 `vagrant up` 命令之前，你不需要下载 box。你可以直接在 Vagrantfile 中指定 box 和提供者，如果还没有的话，Vagrant 会下载它。下面是一个例子，它还设置了内存量和 CPU 数量：

```
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  config.vm.box = "fedora/32-cloud-base"
  config.vm.provider :libvirt do |libvirt|
    libvirt.cpus = 1
    libvirt.memory = 1024
  end
end

```

关于使用 Vagrant、创建你自己的机器和使用不同 box 的更多信息，请参见官方文档 <https://www.vagrantup.com/docs>。

有一个庞大的仓库，你可以随时下载使用这些 box，它们的的官方仓库是 Vagrant Cloud - <https://app.vagrantup.com/boxes/search>。这里有些是基本的操作系统，有些提供完整的功能，如数据库、网络服务器等。

---

via: <https://fedoramagazine.org/vagrant-qemukvm-fedora-devops-sysadmin/>

作者：[Andy Mott](https://fedoramagazine.org/author/amott/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


微软帮我在 Windows 10 电脑上安装了 Ubuntu Linux

配图好评，哈哈，笑死我了

# 微软帮我在 Windows 10 电脑上安装了 Ubuntu Linux

>
> 很多读过我们的技术文章评论的人都知道，每个技术问题的答案都是“切换到 Linux”。而如果你对 Linux 是什么以及它是如何工作的感到好奇，微软可以提供帮助。
>
>
>

如果你曾经经历过痛苦的 Windows 更新，或者不敢置信地看着你的 MacBook 慢到像爬行，并将其风扇切换到巨型喷气式飞机起飞模式，你知道有一个也只有一个答案来解决你的困境：“切换到 Linux”。

当然，我是开玩笑的，但如果你浏览一下这些技术评论，你会发现这个建议是认真的，有一支开源布道者大军定期宣讲圣莱纳斯的福音，以回应关于其他平台的哪怕是最模糊的相关新闻。

你知道吗？我认为那些评论者的观点是合理的。任何有志于了解现代计算环境的人都应该对他们经常使用的操作系统平台以外的平台有一些经验，因为今天你在 Windows、MacOS 和 Linux 中看到的很多东西都来自于相同的 DNA。

为了跟上 Linux 的新动向，我自己每隔一两年就会进行一次这样的练习。所以，想象一下我的惊讶吧，今年我能够在几分钟内搭建一个功能完善的 Ubuntu Linux 机器，而不干扰我当前的 Windows 10 设置。更令人惊讶的是，微软为此做了大部分的工作。

使这一切成为可能的魔法是每台运行 Windows 10 专业版或企业版的 PC 所包含的 Hyper-V 虚拟化软件。（对不起，Windows 10 家庭用户，如果你想玩这些，你得先升级）。用 Hyper-V 的“快速创建”陈列栏，只需点击几下就可以建立一个新的虚拟机，其中包括了独立的 Ubuntu 镜像，而且不是一个而是三个，包括新的 Ubuntu 20.04 版本。

*Hyper-V 快速创建工具包括了三个 Ubuntu Linux 版本*

最重要的是，这些自定义镜像能够在 Hyper-V 增强会话中运行，这意味着你可以选择自定义的显示分辨率，或者在全屏中运行，甚至跨越多个显示器，其性能接近于在裸机上运行的性能。在增强型会话中，你的虚拟机可以共享主机上的 Windows 剪贴板、本地存储和音频硬件。

一旦你把一切都弄好了，你就可以在全屏模式下启动 Ubuntu 虚拟机，并与它一起工作，就像 Windows 10 宿主机不存在一样。

唉，关于所有东西都能正常工作的那部分话并不是说说而已。好消息是，两年前的 Ubuntu 18.04.3 长期支持（LTS）版本工作得很完美，不需要任何操作。但两个较新的版本却让我欲哭无泪。我需要手动编辑一个受保护的 Linux 配置文件，然后才能让增强的会话在最新的 Ubuntu 版本（20.04）中工作，19.10 版本的虚拟机挂了好几次，至少需要重启十几次（包括几次硬重置）才能如期工作。

不过，在一切都结束后，我还是有了三个可以工作的虚拟机，让我对 Ubuntu Linux 中的新功能有了一个相当不错的印象。

* 补充更新，2020 年 6 月 5 日。[通过 Twitter](https://twitter.com/unixterminal/status/1268918077141528579)，@Canonical 的 Ubuntu on WSL 和 Hyper-V 的开发布道师 Hayden Barnes 说，“我们知道 19.10 和 20.04 中的 xrdp bug。20.04 镜像将在即将到来的 20.04.1 LTS 更新中进行修补。19.10 已经接近 EOL，将被放弃。"
* 补充更新 2，2020 年 10 月 1 日。20.04.1 LTS 桌面 Ubuntu 镜像于 2020 年 7 月 31 日发布，但截至 10 月 1 日，它还没有被整合到 Hyper-V 中的快速创建镜像中。

另外，正如我的同事 Mary Branscombe 所指出的那样，包括 Home 在内的所有版本的 Windows 10 都提供了对 Windows Subsystem for Linux（WSL）的访问，该系统在轻量级虚拟机中运行 Linux 内核，并且从 Windows 10 的 2004 版本开始，该系统已经全新升级为 WSL2。正如 WSL2 文档中明确指出的那样，这并不是传统的虚拟机体验，它最适合那些希望获得命令行体验并能够运行 Bash shell 脚本和 GNU/Linux 命令行应用程序的开发者。在 WSL2 环境中运行图形应用程序的能力已列入微软的路线图，应该会在 2020 年底或 2021 年初由 Windows Insiders 进行测试。

如果你想尝试在 Windows 10 中设置一个或多个 Ubuntu 虚拟机进行自己的实验，请看我的另外一篇文章。

---

via: <https://www.zdnet.com/article/microsoft-helped-me-install-ubuntu-linux-on-my-windows-10-pc-and-its-actually-pretty-good/>

作者：[Ed Bott](https://www.zdnet.com/meet-the-team/us/ed-bott/) 译者：[wxy](https://github.com/wxy) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](/article-12674-1.html) 荣誉推出


如何在 Windows 10 中设置 Ubuntu 虚拟机

# 如何在 Windows 10 中设置 Ubuntu 虚拟机

如果你想尝试在 Windows 10 中设置一个或多个 Ubuntu 虚拟机进行自己的实验，请按照以下步骤进行。

### 1、启用 Hyper-V 平台

所有 64 位 Windows 10 专业版、企业版和教育版都内置了这个<ruby> 管理程序 <rp>  （ </rp> <rt>  hypervisor </rt> <rp>  ） </rp></ruby>，但默认情况下并没有启用。在在旧电脑上启用该功能之前，你可能需要打开一些固件选项。有关完整说明，请参阅“[Windows 10：如何启用 Hyper-V 并创建虚拟机](https://www.zdnet.com/article/windows-10-tip-how-to-enable-hyper-v-and-create-virtual-machines/)”。

### 2、使用快速创建工具来下载并安装 Ubuntu 虚拟机

Hyper-V 提供了两种创建新虚拟机的方法。老式的方法是通过一个繁琐的多步骤向导来点击。更简单的解决方案是使用 Hyper-V 的“快速创建”工具。你可以在 Hyper-V 管理器的右侧窗格中找到一个快捷方式，或者你可以直接在 Windows 10 搜索框搜索 `Vmcreate.exe` 命令打开它。

从左侧的陈列栏中选择一个 Ubuntu 版本，然后点击“创建虚拟机”按钮。这将需要下载 1.6GB（18.04.3 LTS）到 2GB（更近的版本）。下载完成后，该“快速创建”功能会提取镜像、配置虚拟机，然后显示一个对话框，告诉你虚拟机已成功创建。

### 3、调整一些设置

虽然你可以通过点击“连接”立即开始，但如果你的 Windows 10 主机上有足够的资源，我建议你点击“编辑设置”，以便你可以先进行一些调整。

*我建议你在第一次运行虚拟机之前编辑一些设置*

从新虚拟机的硬件列表中选择“内存”，然后将分配的 RAM 从其默认值 2048 改为 4096，这应该会带来更好的性能。如果你想要一个固定数量的虚拟内存，你也可以禁用动态内存。你可能还希望将系统磁盘的大小从其默认的 12GB 提升到更多。单击“硬盘驱动器”，然后单击“编辑”，并按照提示操作，直到进行到“扩展”命令那一步。

*根据需要调整分配给你的 Linux 虚拟机的内存*

最后，在“管理”标题下，单击“检查点”，然后清除“使用自动检查点”框。（如果你希望能够回滚更改，可以随时手动创建检查点。）

这些调整完毕后，可以关闭“设置”对话框，并首次打开虚拟机。

### 4、设置你的用户账户

运行 Ubuntu 的系统配置，选择你的默认语言、时区等。当你进入创建登录凭证的界面时，将默认设置保留在“需要我的密码才能登录”。

虽然听起来很方便，但不要启用自动登录功能。如果你选择此选项，将无法使用增强型会话登录，并且你的虚拟机将被迫在一个相对较小的窗口中运行，其默认分辨率无法轻松更改。

这也是运行“系统更新管理器”应用程序并获取最新 Ubuntu 更新的好时机。（点击左下角的 Ubuntu 图标，使用搜索框找到该应用。）

### 5、使用增强版会话登录

如果你在“虚拟机连接”窗口中看到这个对话框，你就会知道已经启动了一个增强的会话：

*将该滑块移至最右侧，以全屏方式运行虚拟机*

滑动开关来选择你的显示分辨率。我喜欢将滑块向右移动，到全屏位置。如果你有多个显示器，并希望虚拟机跨越它们，请选择 “使用所有我的显示器”复选框。单击“连接”继续，打开 Xrdp 登录对话框，如图所示：

*登录到 Xrdp 以启动增强型 Hyper-V 会话*

输入你在创建 Ubuntu Linux 账户时设置的用户名和密码，然后点击“确定”。假设大神们没有生气，你就会登录到你的虚拟机上，可以开始工作了。如果你还没有在你要连接的会话上登录，可能需要第二次输入密码。（如果你得到一个黑屏，尝试按回车键。）

如果你使用 18.04.3 LTS 虚拟机，所有这些都应该可以正常工作。如果你使用的是 Ubuntu 19.10，你可能要经历我所做的同样令人沮丧的一次次重启，然后一切才能正常工作。

对于 Ubuntu 20.04 来说，在基本会话中一切都很正常，但是在我编辑 `/etc/xrdp` 中的 `Xrdp.ini` 文件之前，运行增强会话的选项是不可用的。这是一个受保护的位置，所以我不得不用管理员的凭证打开一个编辑器，这个任务既不直观也不容易。

我必须修改 ini 文件中的两行内容。首先，把 `port=3389` 改为 `port=vsock://-1:3389`。然后把 `use_vsock=true` 改为 `use_vsock=false`。关闭虚拟机连接窗口并再次连接到虚拟机，你应该可以启动增强型会话。但你可能还需要召唤一些小神灵，烧一些香。毕竟，这是 Linux。

（正如我之前所提到的，Canonical/微软团队将在 20.04.1 更新中对该镜像进行修复，所以到时候 xrdp.ini 文件不需要编辑了。）

而你现在就只能靠自己了。你必须找到能让你完成实际工作的生产力软件。如果你像我一样，主要使用微软的服务，准备好接受一大堆的挫折吧。

微软目前没有任何适用于 Linux 的 Office 应用程序，所以你需要从 Web 浏览器访问你的文件。(是的，有 Linux 的 Office 克 隆版，包括 LibreOffice 套件，它是微软 Office 2007 的忠实克隆版。它……勉强够用。)

在 Ubuntu Linux 20.04 中，我可以直接连接 Google Drive，这样那些云端备份的文件就直接出现在文件应用中。还可以使用一个易于安装的 Dropbox 应用。但是 OneDrive 呢？那就不一定了。有一个开源的第三方同步客户端，但安装时需要半打以上的依赖包，我没有耐心也没有信心完成这个过程。

Ubuntu Linux 中的默认浏览器是 Mozilla Firefox，它在 18.04.3 LTS 版本上工作得足够好，但在 20.04 版本中却很卡顿。微软表示，它计划为 Linux 发布基于 Chromium 的新 Edge，但到目前为止，这还只是一个排期。从好的方面来看，有一个做得非常好的微软 Teams 客户端，它可以提供一个原生的 .deb 包来安装。

虽然整体体验出乎意料的流畅，但有太多的时刻，运行 Linux 对我来说感觉略显磕磕绊绊。比如在 20.04 版本中，Firefox 会将包文件下载到一个临时文件夹，在那里无法正常打开。当我弄清楚我必须把这些文件移到下载目录下，一切都好了。（请注意， Google Chrome 浏览器的下载会默认到正确的位置。）

另一方面，如果你的工作日是在谷歌生态系统中度过的，Linux 可能会感到非常舒适。在安装 Chrome 浏览器和设置 G Suite 之后，这个虚拟机基本上就是一个略显廉价的 Chromebook。

我是不是要改用 Linux 作为主要操作系统？呃，不会。但我会让这些虚拟机继续服务，并定期检查。因为你永远不知道会……

---

via: <https://www.zdnet.com/article/microsoft-helped-me-install-ubuntu-linux-on-my-windows-10-pc-and-its-actually-pretty-good/>

作者：[Ed Bott](https://www.zdnet.com/meet-the-team/us/ed-bott/) 译者：[wxy](https://github.com/wxy) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](/article-12677-1.html) 荣誉推出


#新闻拍一拍# IonQ 宣称打造了迄今最强大的量子计算机

Summary: • Sourcegraph：开发人员现在管理的代码比 2010 年多 100 倍 • PHP 8.0 RC1 发布

# #新闻拍一拍# IonQ 宣称打造了迄今最强大的量子计算机

<img src="content_image/album_202010_03_130821qo2z6027ifei6omz.jpg" alt="" title="">

#### IonQ 宣称打造了迄今最强大的量子计算机

IonQ 今日宣布，其已推出“具有 32 个完美的量子比特、且门误差相当低”的最新款量子计算机。参考 IBM 首选的量子基准测试，IonQ 有望达成 400 万的量子体积，因而可将之视作迄今最强大的量子计算机。该公司将当代硬件的量子比特从 11 位提升到了 32 位。不过更重要的是，他们提升了使用全部 32 个量子比特所需的保真度。根据不同应用，客户需要 80~150 个之间的高保真量子位和逻辑门才能体现量子优势。IonQ 的目标是让量子比特数年均至少翻一番。

来源：[cnbeta](https://www.cnbeta.com/articles/tech/1036117.htm)

>
> 拍一拍：虽然还距离实用尚有距离，但是量子技术的进步也太快了。
>
>
>

#### Sourcegraph：开发人员现在管理的代码比 2010 年多 100 倍

Sourcegraph CEO 接受媒体采访时表示，一个典型的组织或开发人员管理的代码量在过去十年中出现增长这并不奇怪，超一半的被调查开发者报告增长（以兆字节计算）超过了一百倍，大部分都来自于所使用的平台和工具的多样性的增加。

来源：[cnbeta](https://www.cnbeta.com/articles/tech/1036227.htm)

>
> 拍一拍：代码会越来越庞大复杂，必然会出现更好的管理工具，跟不上时代人类程序员，只能将工作交给 AI 来编程和管理。
>
>
>

#### PHP 8.0 RC1 发布

PHP 8.0 继续提升性能，引入了 PHP JIT 和其他优化，现在 PHP 已经是一头与 PHP 5.x 的迟钝时代截然不同的野兽了。PHP 8.0 还将 JSON 支持引入 PHP 核心而不是可选的扩展，改进了 GD 图像库、OpenSSL 加密消息语法支持、Zip 改进，以及其他无数的增强功能。预计 11 月底左右完成一般可用性版本。

来源：[cnbeta](https://www.cnbeta.com/articles/tech/1036089.htm)

>
> 拍一拍：PHP 的活力十足，不再是当年那个橡皮小鸭子了。
>
>
>


构建一个即时消息应用（四）：消息

# 构建一个即时消息应用（四）：消息

<img src="content_image/album_202010_04_114458z1p1188epequ686p.jpg" alt="" title="">

本文是该系列的第四篇。

* [第一篇: 模式](/article-11396-1.html)
* [第二篇: OAuth](/article-11510-1.html)
* [第三篇: 对话](/article-12056-1.html)

在这篇文章中，我们将对端点进行编码，以创建一条消息并列出它们，同时还将编写一个端点以更新参与者上次阅读消息的时间。 首先在 `main()` 函数中添加这些路由。

```
router.HandleFunc("POST", "/api/conversations/:conversationID/messages", requireJSON(guard(createMessage)))
router.HandleFunc("GET", "/api/conversations/:conversationID/messages", guard(getMessages))
router.HandleFunc("POST", "/api/conversations/:conversationID/read_messages", guard(readMessages))

```

消息会进入对话，因此端点包含对话 ID。

### 创建消息

该端点处理对 `/api/conversations/{conversationID}/messages` 的 POST 请求，其 JSON 主体仅包含消息内容，并返回新创建的消息。它有两个副作用：更新对话 `last_message_id` 以及更新参与者 `messages_read_at`。

```
func createMessage(w http.ResponseWriter, r *http.Request) {
	var input struct {
		Content string `json:"content"`
	}
	defer r.Body.Close()
	if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	errs := make(map[string]string)
	input.Content = removeSpaces(input.Content)
	if input.Content == "" {
		errs["content"] = "Message content required"
	} else if len([]rune(input.Content)) > 480 {
		errs["content"] = "Message too long. 480 max"
	}
	if len(errs) != 0 {
		respond(w, Errors{errs}, http.StatusUnprocessableEntity)
		return
	}

	ctx := r.Context()
	authUserID := ctx.Value(keyAuthUserID).(string)
	conversationID := way.Param(ctx, "conversationID")

	tx, err := db.BeginTx(ctx, nil)
	if err != nil {
		respondError(w, fmt.Errorf("could not begin tx: %v", err))
		return
	}
	defer tx.Rollback()

	isParticipant, err := queryParticipantExistance(ctx, tx, authUserID, conversationID)
	if err != nil {
		respondError(w, fmt.Errorf("could not query participant existance: %v", err))
		return
	}

	if !isParticipant {
		http.Error(w, "Conversation not found", http.StatusNotFound)
		return
	}

	var message Message
	if err := tx.QueryRowContext(ctx, `
		INSERT INTO messages (content, user_id, conversation_id) VALUES
			($1, $2, $3)
		RETURNING id, created_at
	`, input.Content, authUserID, conversationID).Scan(
		&message.ID,
		&message.CreatedAt,
	); err != nil {
		respondError(w, fmt.Errorf("could not insert message: %v", err))
		return
	}

	if _, err := tx.ExecContext(ctx, `
		UPDATE conversations SET last_message_id = $1
		WHERE id = $2
	`, message.ID, conversationID); err != nil {
		respondError(w, fmt.Errorf("could not update conversation last message ID: %v", err))
		return
	}

	if err = tx.Commit(); err != nil {
		respondError(w, fmt.Errorf("could not commit tx to create a message: %v", err))
		return
	}

	go func() {
		if err = updateMessagesReadAt(nil, authUserID, conversationID); err != nil {
			log.Printf("could not update messages read at: %v\n", err)
		}
	}()

	message.Content = input.Content
	message.UserID = authUserID
	message.ConversationID = conversationID
	// TODO: notify about new message.
	message.Mine = true

	respond(w, message, http.StatusCreated)
}

```

首先，它将请求正文解码为包含消息内容的结构。然后，它验证内容不为空并且少于 480 个字符。

```
var rxSpaces = regexp.MustCompile("\\s+")

func removeSpaces(s string) string {
	if s == "" {
		return s
	}

	lines := make([]string, 0)
	for _, line := range strings.Split(s, "\n") {
		line = rxSpaces.ReplaceAllLiteralString(line, " ")
		line = strings.TrimSpace(line)
		if line != "" {
			lines = append(lines, line)
		}
	}
	return strings.Join(lines, "\n")
}

```

这是删除空格的函数。它遍历每一行，删除两个以上的连续空格，然后回非空行。

验证之后，它将启动一个 SQL 事务。首先，它查询对话中的参与者是否存在。

```
func queryParticipantExistance(ctx context.Context, tx *sql.Tx, userID, conversationID string) (bool, error) {
	if ctx == nil {
		ctx = context.Background()
	}
	var exists bool
	if err := tx.QueryRowContext(ctx, `SELECT EXISTS (
		SELECT 1 FROM participants
		WHERE user_id = $1 AND conversation_id = $2
	)`, userID, conversationID).Scan(&exists); err != nil {
		return false, err
	}
	return exists, nil
}

```

我将其提取到一个函数中，因为稍后可以重用。

如果用户不是对话参与者，我们将返回一个 `404 NOT Found` 错误。

然后，它插入消息并更新对话 `last_message_id`。从这时起，由于我们不允许删除消息，因此 `last_message_id` 不能为 `NULL`。

接下来提交事务，并在 goroutine 中更新参与者 `messages_read_at`。

```
func updateMessagesReadAt(ctx context.Context, userID, conversationID string) error {
	if ctx == nil {
		ctx = context.Background()
	}

	if _, err := db.ExecContext(ctx, `
		UPDATE participants SET messages_read_at = now()
		WHERE user_id = $1 AND conversation_id = $2
	`, userID, conversationID); err != nil {
		return err
	}
	return nil
}

```

在回复这条新消息之前，我们必须通知一下。这是我们将要在下一篇文章中编写的实时部分，因此我在那里留一了个注释。

### 获取消息

这个端点处理对 `/api/conversations/{conversationID}/messages` 的 GET 请求。 它用一个包含会话中所有消息的 JSON 数组进行响应。它还具有更新参与者 `messages_read_at` 的副作用。

```
func getMessages(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	authUserID := ctx.Value(keyAuthUserID).(string)
	conversationID := way.Param(ctx, "conversationID")

	tx, err := db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
	if err != nil {
		respondError(w, fmt.Errorf("could not begin tx: %v", err))
		return
	}
	defer tx.Rollback()

	isParticipant, err := queryParticipantExistance(ctx, tx, authUserID, conversationID)
	if err != nil {
		respondError(w, fmt.Errorf("could not query participant existance: %v", err))
		return
	}

	if !isParticipant {
		http.Error(w, "Conversation not found", http.StatusNotFound)
		return
	}

	rows, err := tx.QueryContext(ctx, `
		SELECT
			id,
			content,
			created_at,
			user_id = $1 AS mine
		FROM messages
		WHERE messages.conversation_id = $2
		ORDER BY messages.created_at DESC
	`, authUserID, conversationID)
	if err != nil {
		respondError(w, fmt.Errorf("could not query messages: %v", err))
		return
	}
	defer rows.Close()

	messages := make([]Message, 0)
	for rows.Next() {
		var message Message
		if err = rows.Scan(
			&message.ID,
			&message.Content,
			&message.CreatedAt,
			&message.Mine,
		); err != nil {
			respondError(w, fmt.Errorf("could not scan message: %v", err))
			return
		}

		messages = append(messages, message)
	}

	if err = rows.Err(); err != nil {
		respondError(w, fmt.Errorf("could not iterate over messages: %v", err))
		return
	}

	if err = tx.Commit(); err != nil {
		respondError(w, fmt.Errorf("could not commit tx to get messages: %v", err))
		return
	}

	go func() {
		if err = updateMessagesReadAt(nil, authUserID, conversationID); err != nil {
			log.Printf("could not update messages read at: %v\n", err)
		}
	}()

	respond(w, messages, http.StatusOK)
}

```

首先，它以只读模式开始一个 SQL 事务。检查参与者是否存在，并查询所有消息。在每条消息中，我们使用当前经过身份验证的用户 ID 来了解用户是否拥有该消息（`mine`）。 然后，它提交事务，在 goroutine 中更新参与者 `messages_read_at` 并以消息响应。

### 读取消息

该端点处理对 `/api/conversations/{conversationID}/read_messages` 的 POST 请求。 没有任何请求或响应主体。 在前端，每次有新消息到达实时流时，我们都会发出此请求。

```
func readMessages(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	authUserID := ctx.Value(keyAuthUserID).(string)
	conversationID := way.Param(ctx, "conversationID")

	if err := updateMessagesReadAt(ctx, authUserID, conversationID); err != nil {
		respondError(w, fmt.Errorf("could not update messages read at: %v", err))
		return
	}

	w.WriteHeader(http.StatusNoContent)
}

```

它使用了与更新参与者 `messages_read_at` 相同的函数。

---

到此为止。实时消息是后台仅剩的部分了。请等待下一篇文章。

* [源代码](https://github.com/nicolasparada/go-messenger-demo)

---

via: <https://nicolasparada.netlify.com/posts/go-messenger-messages/>

作者：[Nicolás Parada](https://nicolasparada.netlify.com/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[gxlct008](https://github.com/gxlct008) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


使用 gosec 检查 Go 代码中的安全问题

Summary: 来学习下 Go 语言的安全检查工具 gosec。

来学习下 Go 语言的安全检查工具 gosec。

# 使用 gosec 检查 Go 代码中的安全问题

>
> 来学习下 Go 语言的安全检查工具 gosec。
>
>
>

<img src="content_image/album_202010_04_125129bh4qxxsyqpvqjtx4.jpg" alt="" title="">

[Go 语言](https://golang.org/)写的代码越来越常见，尤其是在容器、Kubernetes 或云生态相关的开发中。Docker 是最早采用 Golang 的项目之一，随后是 Kubernetes，之后大量的新项目在众多编程语言中选择了 Go。

像其他语言一样，Go 也有它的长处和短处（如安全缺陷）。这些缺陷可能会因为语言本身的缺陷加上程序员编码不当而产生，例如，C 代码中的内存安全问题。

无论它们出现的原因是什么，安全问题都应该在开发过程的早期修复，以免在封装好的软件中出现。幸运的是，静态分析工具可以帮你以更可重复的方式处理这些问题。静态分析工具通过解析用某种编程语言写的代码来找到问题。

这类工具中很多被称为 linter。传统意义上，linter 更注重的是检查代码中编码问题、bug、代码风格之类的问题，它们可能不会发现代码中的安全问题。例如，[Coverity](https://www.synopsys.com/software-integrity/security-testing/static-analysis-sast.html) 是一个很流行的工具，它可以帮助寻找 C/C++ 代码中的问题。然而，也有一些工具专门用来检查源码中的安全问题。例如，[Bandit](https://pypi.org/project/bandit/) 可以检查 Python 代码中的安全缺陷。而 [gosec](https://github.com/securego/gosec) 则用来搜寻 Go 源码中的安全缺陷。`gosec` 通过扫描 Go 的 AST（<ruby> 抽象语法树 <rt>  abstract syntax tree </rt></ruby>）来检查源码中的安全问题。

### 开始使用 gosec

在开始学习和使用 `gosec` 之前，你需要准备一个 Go 语言写的项目。有这么多开源软件，我相信这不是问题。你可以在 GitHub 的 [热门 Golang 仓库](https://github.com/trending/go)中找一个。

本文中，我随机选了 [Docker CE](https://github.com/docker/docker-ce) 项目，但你可以选择任意的 Go 项目。

#### 安装 Go 和 gosec

如果你还没安装 Go，你可以先从仓库中拉取下来。如果你用的是 Fedora 或其他基于 RPM 的 Linux 发行版本：

```
$ dnf install golang.x86_64

```

如果你用的是其他操作系统，请参照 [Golang 安装](https://golang.org/doc/install)页面。

使用 `version` 参数来验证 Go 是否安装成功：

```
$ go version
go version go1.14.6 linux/amd64

```

运行 `go get` 命令就可以轻松地安装 `gosec`：

```
$ go get github.com/securego/gosec/cmd/gosec

```

上面这行命令会从 GitHub 下载 `gosec` 的源码，编译并安装到指定位置。在仓库的 `README` 中你还可以看到[安装该工具的其他方法](https://github.com/securego/gosec#install)。

`gosec` 的源码会被下载到 `$GOPATH` 的位置，编译出的二进制文件会被安装到你系统上设置的 `bin` 目录下。你可以运行下面的命令来查看 `$GOPATH` 和 `$GOBIN` 目录：

```
$ go env | grep GOBIN
GOBIN="/root/go/gobin"
$ go env | grep GOPATH
GOPATH="/root/go"

```

如果 `go get` 命令执行成功，那么 `gosec` 二进制应该就可以使用了：

```
$ ls -l ~/go/bin/
total 9260
-rwxr-xr-x. 1 root root 9482175 Aug 20 04:17 gosec

```

你可以把 `$GOPATH` 下的 `bin` 目录添加到 `$PATH` 中。这样你就可以像使用系统上的其他命令一样来使用 `gosec` 命令行工具（CLI）了。

```
$ which gosec
/root/go/bin/gosec
$

```

使用 `gosec` 命令行工具的 `-help` 选项来看看运行是否符合预期：

```
$ gosec -help

gosec - Golang security checker

gosec analyzes Go source code to look for common programming mistakes that
can lead to security problems.

VERSION: dev
GIT TAG:
BUILD DATE:

USAGE:

```

之后，创建一个目录，把源码下载到这个目录作为实例项目（本例中，我用的是 Docker CE）：

```
$ mkdir gosec-demo
$ cd gosec-demo/
$ pwd
/root/gosec-demo
$ git clone https://github.com/docker/docker-ce.git
Cloning into 'docker-ce'...
remote: Enumerating objects: 1271, done.
remote: Counting objects: 100% (1271/1271), done.
remote: Compressing objects: 100% (722/722), done.
remote: Total 431003 (delta 384), reused 981 (delta 318), pack-reused 429732
Receiving objects: 100% (431003/431003), 166.84 MiB | 28.94 MiB/s, done.
Resolving deltas: 100% (221338/221338), done.
Updating files: 100% (10861/10861), done.

```

代码统计工具（本例中用的是 `cloc`）显示这个项目大部分是用 Go 写的，恰好迎合了 `gosec` 的功能。

```
$ ./cloc /root/gosec-demo/docker-ce/
   10771 text files.
    8724 unique files.
    2560 files ignored.

-----------------------------------------------------------------------------------
Language                         files          blank        comment           code
-----------------------------------------------------------------------------------
Go                                7222         190785         230478        1574580
YAML                                37           4831            817         156762
Markdown                           529          21422              0          67893
Protocol Buffers                   149           5014          16562          10071

```

### 使用默认选项运行 gosec

在 Docker CE 项目中使用默认选项运行 `gosec`，执行 `gosec ./...` 命令。屏幕上会有很多输出内容。在末尾你会看到一个简短的 “Summary”，列出了浏览的文件数、所有文件的总行数，以及源码中发现的问题数。

```
$ pwd
/root/gosec-demo/docker-ce
$ time gosec ./...
[gosec] 2020/08/20 04:44:15 Including rules: default
[gosec] 2020/08/20 04:44:15 Excluding rules: default
[gosec] 2020/08/20 04:44:15 Import directory: /root/gosec-demo/docker-ce/components/engine/opts
[gosec] 2020/08/20 04:44:17 Checking package: opts
[gosec] 2020/08/20 04:44:17 Checking file: /root/gosec-demo/docker-ce/components/engine/opts/address_pools.go
[gosec] 2020/08/20 04:44:17 Checking file: /root/gosec-demo/docker-ce/components/engine/opts/env.go
[gosec] 2020/08/20 04:44:17 Checking file: /root/gosec-demo/docker-ce/components/engine/opts/hosts.go

# End of gosec run

Summary:
   Files: 1278
   Lines: 173979
   Nosec: 4
  Issues: 644

real    0m52.019s
user    0m37.284s
sys     0m12.734s
$

```

滚动屏幕你会看到不同颜色高亮的行：红色表示需要尽快查看的高优先级问题，黄色表示中优先级的问题。

#### 关于误判

在开始检查代码之前，我想先分享几条基本原则。默认情况下，静态检查工具会基于一系列的规则对测试代码进行分析，并报告出它们发现的*所有*问题。这是否意味着工具报出来的每一个问题都需要修复？非也。这个问题最好的解答者是设计和开发这个软件的人。他们最熟悉代码，更重要的是，他们了解软件会在什么环境下部署以及会被怎样使用。

这个知识点对于判定工具标记出来的某段代码到底是不是安全缺陷至关重要。随着工作时间和经验的积累，你会慢慢学会怎样让静态分析工具忽略非安全缺陷，使报告内容的可执行性更高。因此，要判定 `gosec` 报出来的某个问题是否需要修复，让一名有经验的开发者对源码做人工审计会是比较好的办法。

#### 高优先级问题

从输出内容看，`gosec` 发现了 Docker CE 的一个高优先级问题，它使用的是低版本的 TLS（<ruby> 传输层安全 <rt>  Transport Layer Security </rt> <rt> </rt></ruby>）。无论什么时候，使用软件和库的最新版本都是确保它更新及时、没有安全问题的最好的方法。

```
[/root/gosec-demo/docker-ce/components/engine/daemon/logger/splunk/splunk.go:173] - G402 (CWE-295): TLS MinVersion too low. (Confidence: HIGH, Severity: HIGH)
    172:
  > 173:        tlsConfig := &tls.Config{}
    174:

```

它还发现了一个弱随机数生成器。它是不是一个安全缺陷，取决于生成的随机数的使用方式。

```
[/root/gosec-demo/docker-ce/components/engine/pkg/namesgenerator/names-generator.go:843] - G404 (CWE-338): Use of weak random number generator (math/rand instead of crypto/rand) (Confidence: MEDIUM, Severity: HIGH)
    842: begin:
  > 843:        name := fmt.Sprintf("%s_%s", left[rand.Intn(len(left))], right[rand.Intn(len(right))])
    844:        if name == "boring_wozniak" /* Steve Wozniak is not boring */ {

```

#### 中优先级问题

这个工具还发现了一些中优先级问题。它标记了一个通过与 `tar` 相关的解压炸弹这种方式实现的潜在的 DoS 威胁，这种方式可能会被恶意的攻击者利用。

```
[/root/gosec-demo/docker-ce/components/engine/pkg/archive/copy.go:357] - G110 (CWE-409): Potential DoS vulnerability via decompression bomb (Confidence: MEDIUM, Severity: MEDIUM)
    356:
  > 357:                        if _, err = io.Copy(rebasedTar, srcTar); err != nil {
    358:                                w.CloseWithError(err)

```

它还发现了一个通过变量访问文件的问题。如果恶意使用者能访问这个变量，那么他们就可以改变变量的值去读其他文件。

```
[/root/gosec-demo/docker-ce/components/cli/cli/context/tlsdata.go:80] - G304 (CWE-22): Potential file inclusion via variable (Confidence: HIGH, Severity: MEDIUM)
    79:         if caPath != "" {
  > 80:                 if ca, err = ioutil.ReadFile(caPath); err != nil {
    81:                         return nil, err

```

文件和目录通常是操作系统安全的最基础的元素。这里，`gosec` 报出了一个可能需要你检查目录的权限是否安全的问题。

```
[/root/gosec-demo/docker-ce/components/engine/contrib/apparmor/main.go:41] - G301 (CWE-276): Expect directory permissions to be 0750 or less (Confidence: HIGH, Severity: MEDIUM)
    40:         // make sure /etc/apparmor.d exists
  > 41:         if err := os.MkdirAll(path.Dir(apparmorProfilePath), 0755); err != nil {
    42:                 log.Fatal(err)

```

你经常需要在源码中启动命令行工具。Go 使用内建的 exec 库来实现。仔细地分析用来调用这些工具的变量，就能发现安全缺陷。

```
[/root/gosec-demo/docker-ce/components/engine/testutil/fakestorage/fixtures.go:59] - G204 (CWE-78): Subprocess launched with variable (Confidence: HIGH, Severity: MEDIUM)
    58:
  > 59:              cmd := exec.Command(goCmd, "build", "-o", filepath.Join(tmp, "httpserver"), "github.com/docker/docker/contrib/httpserver")
    60:                 cmd.Env = append(os.Environ(), []string{

```

#### 低优先级问题

在这个输出中，gosec 报出了一个 `unsafe` 调用相关的低优先级问题，这个调用会绕开 Go 提供的内存保护。再仔细分析下你调用 `unsafe` 的方式，看看是否有被别人利用的可能性。

```
[/root/gosec-demo/docker-ce/components/engine/pkg/archive/changes_linux.go:264] - G103 (CWE-242): Use of unsafe calls should be audited (Confidence: HIGH, Severity: LOW)
    263:        for len(buf) > 0 {
  > 264:                dirent := (*unix.Dirent)(unsafe.Pointer(&buf[0]))
    265:                buf = buf[dirent.Reclen:]

[/root/gosec-demo/docker-ce/components/engine/pkg/devicemapper/devmapper_wrapper.go:88] - G103 (CWE-242): Use of unsafe calls should be audited (Confidence: HIGH, Severity: LOW)
    87: func free(p *C.char) {
  > 88:         C.free(unsafe.Pointer(p))
    89: }

```

它还标记了源码中未处理的错误。源码中出现的错误你都应该处理。

```
[/root/gosec-demo/docker-ce/components/cli/cli/command/image/build/context.go:172] - G104 (CWE-703): Errors unhandled. (Confidence: HIGH, Severity: LOW)
    171:                err := tar.Close()
  > 172:                os.RemoveAll(dockerfileDir)
    173:                return err

```

### 自定义 gosec 扫描

使用 `gosec` 的默认选项会带来很多的问题。然而，经过人工审计，随着时间推移你会掌握哪些问题是不需要标记的。你可以自己指定排除和包含哪些测试。

我上面提到过，`gosec` 是基于一系列的规则从 Go 源码中查找问题的。下面是它使用的完整的[规则](https://github.com/securego/gosec#available-rules)列表：

* G101：查找硬编码凭证
* G102：绑定到所有接口
* G103：审计 `unsafe` 块的使用
* G104：审计未检查的错误
* G106：审计 `ssh.InsecureIgnoreHostKey` 的使用
* G107: 提供给 HTTP 请求的 url 作为污点输入
* G108: `/debug/pprof` 上自动暴露的剖析端点
* G109: `strconv.Atoi` 转换到 int16 或 int32 时潜在的整数溢出
* G110: 潜在的通过解压炸弹实现的 DoS
* G201：SQL 查询构造使用格式字符串
* G202：SQL 查询构造使用字符串连接
* G203：在 HTML 模板中使用未转义的数据
* G204：审计命令执行情况
* G301：创建目录时文件权限分配不合理
* G302：使用 `chmod` 时文件权限分配不合理
* G303：使用可预测的路径创建临时文件
* G304：通过污点输入提供的文件路径
* G305：提取 zip/tar 文档时遍历文件
* G306: 写到新文件时文件权限分配不合理
* G307: 把返回错误的函数放到 `defer` 内
* G401：检测 DES、RC4、MD5 或 SHA1 的使用
* G402：查找错误的 TLS 连接设置
* G403：确保最小 RSA 密钥长度为 2048 位
* G404：不安全的随机数源（`rand`）
* G501：导入黑名单列表：crypto/md5
* G502：导入黑名单列表：crypto/des
* G503：导入黑名单列表：crypto/rc4
* G504：导入黑名单列表：net/http/cgi
* G505：导入黑名单列表：crypto/sha1
* G601: 在 `range` 语句中使用隐式的元素别名

#### 排除指定的测试

你可以自定义 `gosec` 来避免对已知为安全的问题进行扫描和报告。你可以使用 `-exclude` 选项和上面的规则编号来忽略指定的问题。

例如，如果你不想让 `gosec` 检查源码中硬编码凭证相关的未处理的错误，那么你可以运行下面的命令来忽略这些错误：

```
$ gosec -exclude=G104 ./...
$ gosec -exclude=G104,G101 ./...

```

有时候你知道某段代码是安全的，但是 `gosec` 还是会报出问题。然而，你又不想完全排除掉整个检查，因为你想让 `gosec` 检查新增的代码。通过在你已知为安全的代码块添加 `#nosec` 标记可以避免 `gosec` 扫描。这样 `gosec` 会继续扫描新增代码，而忽略掉 `#nosec` 标记的代码块。

#### 运行指定的检查

另一方面，如果你只想检查指定的问题，你可以通过 `-include` 选项和规则编号来告诉 `gosec` 运行哪些检查：

```
$ gosec -include=G201,G202 ./...

```

#### 扫描测试文件

Go 语言自带对测试的支持，通过单元测试来检验一个元素是否符合预期。在默认模式下，`gosec` 会忽略测试文件，你可以使用 `-tests` 选项把它们包含进来：

```
gosec -tests ./...

```

#### 修改输出的格式

找出问题只是它的一半功能；另一半功能是把它检查到的问题以用户友好同时又方便工具处理的方式报告出来。幸运的是，`gosec` 可以用不同的方式输出。例如，如果你想看 JSON 格式的报告，那么就使用 `-fmt` 选项指定 JSON 格式并把结果保存到 `results.json` 文件中：

```
$ gosec -fmt=json -out=results.json ./...

$ ls -l results.json
-rw-r--r--. 1 root root 748098 Aug 20 05:06 results.json
$

         {
             "severity": "LOW",
             "confidence": "HIGH",
             "cwe": {
                 "ID": "242",
                 "URL": "https://cwe.mitre.org/data/definitions/242.html"
             },
             "rule_id": "G103",
             "details": "Use of unsafe calls should be audited",
             "file": "/root/gosec-demo/docker-ce/components/engine/daemon/graphdriver/graphtest/graphtest_unix.go",
             "code": "304: \t// Cast to []byte\n305: \theader := *(*reflect.SliceHeader)(unsafe.Pointer(\u0026buf))\n306: \theader.      Len *= 8\n",
             "line": "305",
             "column": "36"
         },

```

### 用 gosec 检查容易被发现的问题

静态检查工具不能完全代替人工代码审计。然而，当代码量变大、有众多开发者时，这样的工具往往有助于以可重复的方式找出容易被发现的问题。它对于帮助新开发者识别和在编码时避免引入这些安全缺陷很有用。

---

via: <https://opensource.com/article/20/9/gosec>

作者：[Gaurav Kamathe](https://opensource.com/users/gkamathe) 选题：[lujun9972](https://github.com/lujun9972) 译者：[lxbowlf](https://github.com/lxbwolf) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


Windows 的 Linux 子系统（WSL）即将带来图形化应用

Summary: 按照现在的发展速度，我对 Windows 11 可能会运行在 Linux 之上的“疯狂”预测，也许会成为现实!

# Windows 的 Linux 子系统（WSL）即将带来图形化应用

>
> Windows 上的 Linux 正在继续发展，功能越来越强大。现在，图形化的 Linux 程序正在被整合到 WSL 中。
>
>
>

<img src="content_image/album_202010_04_224625e5kvrjksnkkzlhos.jpg" alt="" title="">

在微软 Build 2020 虚拟开发者大会上，微软 CEO 萨提亚·纳德拉宣布 Windows 的 Linux 子系统（WSL）2.0 [即将支持 Linux GUI 和应用程序](https://www.zdnet.com/article/microsoft-linux-gui-apps-coming-to-windows-10-wsl-along-with-gpu-access/)。现在这一天比以往任何时候都要近。在最近的 X.Org 开发者大会（XDC）上，微软合作伙伴开发者负责人 Steve Pronovost 透露，微软已经可以在 WSL 中运行图形化的 Linux 应用。

一直以来，虽然都可以在 WSL 上运行 [GIMP 图形编辑器](https://www.gimp.org/)、[Evolution 电子邮件客户端](https://wiki.gnome.org/Apps/Evolution)和 [LibreOffice](https://www.libreoffice.org/) 等 Linux 图形程序，但这并不容易。你必须安装一个第三方 X Window 显示服务器，比如 Windows 10 中的 [VcXsrv Windows X Server](https://sourceforge.net/projects/vcxsrv/)，然后[对 Windows 和 Linux 做一些调整，让它们顺利地一起工作](https://techcommunity.microsoft.com/t5/windows-dev-appconsult/running-wsl-gui-apps-on-windows-10/ba-p/1493242)。[X Window 系统](http://www.opengroup.org/tech/desktop/x-window-system/)几乎是所有 Linux 图形用户界面的基础。

现在，微软已经将 [Wayland 显示服务器](https://www.linux-magazine.com/Online/Features/Is-Wayland-the-New-X)移植到 WSL 中。Wayland 是最流行的 X Window 兼容的显示服务器。在 WSL2 中，它通过远程桌面协议（RDP）连接将图形化的 Linux 应用程序连接到主 Windows 显示器上。这意味着你可以在同一个桌面屏幕上同时运行 Linux 和 Windows GUI 应用程序。

Pronovost 解释道：

>
> WSL 本质上是在 Windows 托管的虚拟机里面运行 Linux，我们将应用程序（控制台程序，现在还有 GUI 程序）整合回你的 Windows 桌面，这样你就可以在统一的体验里面同时运行 Win32 和 Linux 应用程序。由于 Linux 是在虚拟机中运行，我们不能运行期望直接访问 GPU 的原生 GPU 驱动程序（除非我们做一些类似于离散设备分配的事情，并将其中一个宿主机 GPU 分配给虚拟机......但那样宿主机将失去对该 GPU 的访问！）。有了[GPU-PV（GPU 准虚拟化）](https://petri.com/microsoft-announces-gpu-hardware-acceleration-and-gui-app-support-for-wsl-2-at-build-2020)，我们基本上可以在 Linux 中投射宿主机 GPU，让 Linux 和 Windows 进程共享同一个物理 GPU，而不需要固定的资源分区。
>
>
>

微软 WSL 项目经理 Craig Loewen 在 Twitter 上补充道，使用第三方 X 服务器和内置 Wayland 服务器的关键区别在于。“[你不需要启动显示服务器](https://twitter.com/craigaloewen/status/1308452901266751488)，我们会为你处理。”此外，它还带有“与 Windows 的完美集成”，例如投影和 Linux 图标支持。

Loewen 还表示，你可以在其中运行 Linux Web 浏览器。“我们还没有用完整的桌面环境（DE）对它进行充分测试，因为我们想先专注于运行经常被问及的应用，主要是 IDE（集成开发环境），这样你就可以在一个完整的 Linux 环境中运行这些应用，”他说。

不过，先别太兴奋。Loewen 继续说道：“我们还没有制定测试通道的时间表，不过，这项工作通常会在接下来几个月内提供给 Insiders 试用。”

[微软将 Linux 整合到 Windows 中](https://www.zdnet.com/article/2020-will-be-the-year-of-linux-on-the-windows-desktop/)已经有一段时间了。四年前，微软推出了 WSL，[将 Linux Bash shell 带到了 Windows 10 中](https://www.zdnet.com/article/microsoft-to-show-bash-on-linux-running-on-windows-10/)。通过 Bash 和 WSL，你可以运行大多数 [Linux shell 工具](http://blog.dustinkirkland.com/2016/03/ubuntu-on-windows.html)和流行的 Linux 编程语言。

随着时间的推移，Linux 更成为 Windows 桌面上的一等公民。多个 Linux 发行版，从 Ubuntu 开始，随后是红帽 Fedora 和 SUSE Linux 企业桌面版（SLED） 都进入了 Windows 商店。随后，微软用 WSL 2 取代了将 Linux 内核调用转换为 Windows 调用的 WSL 翻译层。这一更新是在精简版 Hyper-V 管理程序上运行的[微软自己的 Linux 内核](https://www.zdnet.com/article/hell-freezing-over-microsoft-releases-its-own-linux-for-windows/)附带的。

最近，[从 Windows 10 Insider Preview build 20211 开始，Windows 用户可以访问 Linux 文件系统](https://devblogs.microsoft.com/commandline/access-linux-filesystems-in-windows-and-wsl-2/)。这包括访问 Windows 本身不支持的 Linux 文件系统，例如 ext4。这也意味着，如果你用不同的磁盘双启动 Windows 和 Linux，现在可以从 Windows 访问 Linux 文件。有了这个功能，你可以通过管理权限从 Windows 文件资源管理器和 PowerShell 窗口访问 Linux 文件。

按照现在的发展速度，我对 [Windows 11 可能会运行在 Linux 之上](https://www.computerworld.com/article/3438856/call-me-crazy-but-windows-11-could-run-on-linux.html)的“疯狂”预测，也许会成为现实!


构建一个即时消息应用（五）：实时消息

Summary: 对于实时消息，我们将使用 服务器发送事件。

对于实时消息，我们将使用 服务器发送事件。

# 构建一个即时消息应用（五）：实时消息

本文是该系列的第五篇。

* [第一篇: 模式](/article-11396-1.html)
* [第二篇: OAuth](/article-11510-1.html)
* [第三篇: 对话](/article-12056-1.html)
* [第四篇: 消息](/article-12680-1.html)

对于实时消息，我们将使用 <ruby>   服务器发送事件  <rt>  Server-Sent Events </rt></ruby>。这是一个打开的连接，我们可以在其中传输数据流。我们会有个端点，用户会在其中订阅发送给他的所有消息。

### 消息户端

在 HTTP 部分之前，让我们先编写一个<ruby> 映射 <rt>  map </rt></ruby> ，让所有客户端都监听消息。 像这样全局初始化：

```
type MessageClient struct {
    Messages chan Message
    UserID   string
}

var messageClients sync.Map

```

### 已创建的新消息

还记得在 [上一篇文章](/article-12680-1.html) 中，当我们创建这条消息时，我们留下了一个 “TODO” 注释。在那里，我们将使用这个函数来调度一个 goroutine。

```
go messageCreated(message)

```

把这行代码插入到我们留注释的位置。

```
func messageCreated(message Message) error {
    if err := db.QueryRow(`
 SELECT user\_id FROM participants
 WHERE user\_id != $1 and conversation\_id = $2
 `, message.UserID, message.ConversationID).
    Scan(&message.ReceiverID); err != nil {
        return err
    }

    go broadcastMessage(message)

    return nil
}

func broadcastMessage(message Message) {
    messageClients.Range(func(key, \_ interface{}) bool {
        client := key.(\*MessageClient)
        if client.UserID == message.ReceiverID {
            client.Messages <- message
        }
        return true
    })
}

```

该函数查询接收者 ID（其他参与者 ID），并将消息发送给所有客户端。

### 订阅消息

让我们转到 `main()` 函数并添加以下路由：

```
router.HandleFunc("GET", "/api/messages", guard(subscribeToMessages))

```

此端点处理 `/api/messages` 上的 GET 请求。请求应该是一个 [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) 连接。它用一个事件流响应，其中的数据是 JSON 格式的。

```
func subscribeToMessages(w http.ResponseWriter, r \*http.Request) {
    if a := r.Header.Get("Accept"); !strings.Contains(a, "text/event-stream") {
        http.Error(w, "This endpoint requires an EventSource connection", http.StatusNotAcceptable)
        return
    }

    f, ok := w.(http.Flusher)
    if !ok {
        respondError(w, errors.New("streaming unsupported"))
        return
    }

    ctx := r.Context()
    authUserID := ctx.Value(keyAuthUserID).(string)

    h := w.Header()
    h.Set("Cache-Control", "no-cache")
    h.Set("Connection", "keep-alive")
    h.Set("Content-Type", "text/event-stream")

    messages := make(chan Message)
    defer close(messages)

    client := &MessageClient{Messages: messages, UserID: authUserID}
    messageClients.Store(client, nil)
    defer messageClients.Delete(client)

    for {
        select {
        case <-ctx.Done():
            return
        case message := <-messages:
            if b, err := json.Marshal(message); err != nil {
                log.Printf("could not marshall message: %v\n", err)
                fmt.Fprintf(w, "event: error\ndata: %v\n\n", err)
            } else {
                fmt.Fprintf(w, "data: %s\n\n", b)
            }
            f.Flush()
        }
    }
}

```

首先，它检查请求头是否正确，并检查服务器是否支持流式传输。我们创建一个消息通道，用它来构建一个客户端，并将其存储在客户端映射中。每当创建新消息时，它都会进入这个通道，因此我们可以通过 `for-select` 循环从中读取。

<ruby> 服务器发送事件 <rt>  Server-Sent Events </rt></ruby>使用以下格式发送数据：

```
data: some data here\n\n

```

我们以 JSON 格式发送：

```
data: {"foo":"bar"}\n\n

```

我们使用 `fmt.Fprintf()` 以这种格式写入响应<ruby> 写入器 <rt>  writter </rt></ruby>，并在循环的每次迭代中刷新数据。

这个循环会一直运行，直到使用请求上下文关闭连接为止。我们延迟了通道的关闭和客户端的删除，因此，当循环结束时，通道将被关闭，客户端不会收到更多的消息。

注意，<ruby> 服务器发送事件 <rt>  Server-Sent Events </rt></ruby>（EventSource）的 JavaScript API 不支持设置自定义请求头?，所以我们不能设置 `Authorization: Bearer <token>`。这就是为什么 `guard()` 中间件也会从 URL 查询字符串中读取令牌的原因。

---

实时消息部分到此结束。我想说的是，这就是后端的全部内容。但是为了编写前端代码，我将再增加一个登录端点：一个仅用于开发的登录。

* [源代码](https://github.com/nicolasparada/go-messenger-demo)

---

via: <https://nicolasparada.netlify.com/posts/go-messenger-realtime-messages/>

作者：[Nicolás Parada](https://nicolasparada.netlify.com/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[gxlct008](https://github.com/gxlct008) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


awk 实用学习指南

Summary: 下载我们的电子书，学习如何更好地使用 awk。

awk 三位作者写的 <<awk 程序设计语言>> 也挺不错的, 有人还把它翻译成了中文, github.com / wuzhouhui / awk

下载我们的电子书，学习如何更好地使用 awk。

# awk 实用学习指南

>
> 下载我们的电子书，学习如何更好地使用 `awk`。
>
>
>

<img src="content_image/album_202010_05_100648zcubcpww627627nw.jpg" alt="" title="">

在众多 [Linux](https://opensource.com/resources/linux) 命令中，`sed`、`awk` 和 `grep` 恐怕是其中最经典的三个命令了。它们引人注目或许是由于名字发音与众不同，也可能是它们无处不在，甚至是因为它们存在已久，但无论如何，如果要问哪些命令很有 Linux 风格，这三个命令是当之无愧的。其中 `sed` 和 `grep` 已经有很多简洁的标准用法了，但 `awk` 的使用难度却相对突出。

在日常使用中，通过 `sed` 实现字符串替换、通过 `grep` 实现过滤，这些都是司空见惯的操作了，但 `awk` 命令相对来说是用得比较少的。在我看来，可能的原因是大多数人都只使用 `sed` 或者 `grep` 的一些变化实现某些功能，例如：

```
$ sed -e 's/foo/bar/g' file.txt
$ grep foo file.txt

```

因此，尽管你可能会觉得 `sed` 和 `grep` 使用起来更加顺手，但实际上它们还有更多更强大的作用没有发挥出来。当然，我们没有必要在这两个命令上钻研得很深入，但我有时会好奇自己“学习”命令的方式。很多时候我会记住一整串命令“咒语”，而不会去了解其中的运作过程，这就让我产生了一种很熟悉命令的错觉，我可以随口说出某个命令的好几个选项参数，但这些参数具体有什么作用，以及它们的相关语法，我都并不明确。

这大概就是很多人对 `awk` 缺乏了解的原因了。

### 为使用而学习 awk

`awk` 并不深奥。它是一种相对基础的编程语言，因此你可以把它当成一门新的编程语言来学习：使用一些基本命令来熟悉语法、了解语言中的关键字并实现更复杂的功能，然后再多加练习就可以了。

### awk 是如何解析输入内容的

`awk` 的本质是将输入的内容看作是一个数组。当 `awk` 扫描一个文本文件时，会把每一行作为一条<ruby> 记录 <rt>  record </rt></ruby>，每一条记录中又分割为多个<ruby> 字段 <rt>  field </rt></ruby>。`awk` 记录了各条记录各个字段的信息，并通过内置变量 `NR`（记录数） 和 `NF`（字段数） 来调用相关信息。例如一下这个命令可以查看文件的行数：

```
$ awk 'END { print NR;}' example.txt
36

```

从上面的命令可以看出 `awk` 的基本语法，无论是一个单行命令还是一整个脚本，语法都是这样的：

```
模式或关键字 { 操作 }

```

在上面的例子中，`END` 是一个关键字而不是模式，与此类似的另一个关键字是 `BEGIN`。使用 `BEGIN` 或 `END` 可以让 `awk` 在解析内容前或解析内容后执行大括号中指定的操作。

你可以使用<ruby> 模式 <rt>  pattern </rt></ruby>作为过滤器或限定符，这样 `awk` 只会对匹配模式的对应记录执行指定的操作。以下这个例子就是使用 `awk` 实现 `grep` 命令在文件中查找“Linux”字符串的功能：

```
$ awk '/Linux/ { print $0; }' os.txt
OS: CentOS Linux (10.1.1.8)
OS: CentOS Linux (10.1.1.9)
OS: Red Hat Enterprise Linux (RHEL) (10.1.1.11)
OS: Elementary Linux (10.1.2.4)
OS: Elementary Linux (10.1.2.5)
OS: Elementary Linux (10.1.2.6)

```

`awk` 会将文件中的每一行作为一条记录，将一条记录中的每个单词作为一个字段，默认情况下会以空格作为<ruby> 字段分隔符 <rt>  field separator </rt></ruby>（`FS`）切割出记录中的字段。如果想要使用其它内容作为分隔符，可以使用 `--field-separator` 选项指定分隔符：

```
$ awk --field-separator ':' '/Linux/ { print $2; }' os.txt
 CentOS Linux (10.1.1.8)
 CentOS Linux (10.1.1.9)
 Red Hat Enterprise Linux (RHEL) (10.1.1.11)
 Elementary Linux (10.1.2.4)
 Elementary Linux (10.1.2.5)
 Elementary Linux (10.1.2.6)

```

在上面的例子中，可以看到在 `awk` 处理后每一行的行首都有一个空格，那是因为在源文件中每个冒号（`:`）后面都带有一个空格。和 `cut` 有所不同的是，`awk` 可以指定一个字符串作为分隔符，就像这样：

```
$ awk --field-separator ': ' '/Linux/ { print $2; }' os.txt
CentOS Linux (10.1.1.8)
CentOS Linux (10.1.1.9)
Red Hat Enterprise Linux (RHEL) (10.1.1.11)
Elementary Linux (10.1.2.4)
Elementary Linux (10.1.2.5)
Elementary Linux (10.1.2.6)

```

### awk 中的函数

可以通过这样的语法在 `awk` 中自定义函数：

```
函数名称(参数) { 操作 }

```

函数的好处在于只需要编写一次就可以多次复用，因此函数在脚本中起到的作用会比在构造单行命令时大。同时 `awk` 自身也带有很多预定义的函数，并且工作原理和其它编程语言或电子表格一样。你只需要了解函数需要接受什么参数，就可以放心使用了。

`awk` 中提供了数学运算和字符串处理的相关函数。数学运算函数通常比较简单，传入一个数字，它就会传出一个结果：

```
$ awk 'BEGIN { print sqrt(1764); }'
42

```

而字符串处理函数则稍微复杂一点，但 [GNU awk 手册](https://www.gnu.org/software/gawk/manual/gawk.html)中也有充足的文档。例如 `split()` 函数需要传入一个待分割的单一字段、一个用于存放分割结果的数组，以及用于分割的<ruby> 定界符 <rt>  delimiter </rt></ruby>。

例如前面示例中的输出内容，每条记录的末尾都包含了一个 IP 地址。由于变量 `NF` 代表的是每条记录的字段数量，刚好对应的是每条记录中最后一个字段的序号，因此可以通过引用 `NF` 将每条记录的最后一个字段传入 `split()` 函数：

```
$ awk --field-separator ': ' '/Linux/ { split($NF, IP, "."); print "subnet: " IP[3]; }' os.txt
subnet: 1
subnet: 1
subnet: 1
subnet: 2
subnet: 2
subnet: 2

```

还有更多的函数，没有理由将自己限制在每个 `awk` 代码块中。你可以在终端中使用 `awk` 构建复杂的管道，也可以编写 `awk` 脚本来定义和使用你自己的函数。

### 下载电子书

使用 `awk` 本身就是一个学习 `awk` 的过程，即使某些操作使用 `sed`、`grep`、`cut`、`tr` 命令已经完全足够了，也可以尝试使用 `awk` 来实现。只要熟悉了 `awk`，就可以在 Bash 中自定义一些 `awk` 函数，进而解析复杂的数据。

[下载我们的这本电子书](https://opensource.com/downloads/awk-ebook)（需注册）学习并开始使用 `awk` 吧！

---

via: <https://opensource.com/article/20/9/awk-ebook>

作者：[Seth Kenlon](https://opensource.com/users/seth) 选题：[lujun9972](https://github.com/lujun9972) 译者：[HankChow](https://github.com/hankchow) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


什么是 IPv6，为什么我们还未普及？

# 什么是 IPv6，为什么我们还未普及？

>
> 自 1998 年以来，IPv6 一直在努力解决 IPv4 可用 IP 地址的不足的问题，然而尽管 IPv6 在效率和安全方面具有优势，但其采用速度仍然缓慢。
>
>
>

<img src="content_image/album_202010_05_174424teevv5ep7ee8lslh.jpg" alt="" title="">

在大多数情况下，已经没有人一再对互联网地址耗尽的可怕境况发出警告，因为，从互联网协议版本 4（IPv4）的世界到 IPv6 的迁移，虽然缓慢，但已经坚定地开始了，并且相关软件已经到位，以防止许多人预测的地址耗竭。

但在我们看到 IPv6 的现状和发展方向之前，让我们先回到互联网寻址的早期。

### 什么是 IPv6，为什么它很重要？

IPv6 是最新版本的<ruby> 互联网协议 <rt>  Internet Protocol </rt></ruby>（IP），它可以跨互联网识别设备，从而确定它们的位置。每一个使用互联网的设备都要通过自己的 IP 地址来识别，以便可以通过互联网通信。在这方面，它就像你需要知道街道地址和邮政编码一样，以便邮寄信件。

之前的版本 IPv4 采用 32 位寻址方案，可以支持 43 亿台设备，本以为已经足够。然而，互联网、个人电脑、智能手机以及现在物联网设备的发展证明，这个世界需要更多的地址。

幸运的是，<ruby> 互联网工程任务组 <rt>  Internet Engineering Task Force </rt></ruby>（IETF）在 20 年前就认识到了这一点。1998 年，它创建了 IPv6，使用 128 位寻址方式来支持大约 340 <ruby> 亿亿亿 <rt>  trillion trillion </rt></ruby>（或者 2 的 128 次幂，如果你喜欢用这种表示方式的话）。IPv4 的地址可表示为四组一至三位十进制数，IPv6 则使用八组四位十六进制数字，用冒号隔开。

### IPv6 的好处是什么？

IETF 在其工作中为 IPv6 加入了对 IPv4 增强的功能。IPv6 协议可以更有效地处理数据包，提高性能和增加安全性。它使互联网服务提供商（ISP）能够通过使他们的路由表更有层次性来减少其大小。

### 网络地址转换（NAT）和 IPv6

IPv6 的采用被推迟，部分原因是<ruby> 网络地址转换 <rt>  network address translation </rt></ruby>（NAT）导致的，NAT 可以将私有 IP 地址转化为公共 IP 地址。这样一来，拥有私有 IP 地址的企业的机器就可以向位于私有网络之外拥有公共 IP 地址的机器发送和接收数据包。

如果没有 NAT，拥有数千台或数万台计算机的大公司如果要与外界通信，就会吞噬大量的公有 IPv4 地址。但是这些 IPv4 地址是有限的，而且接近枯竭，以至于不得不限制分配。

NAT 有助于缓解这个问题。有了 NAT，成千上万的私有地址计算机可以通过防火墙或路由器等 NAT 设备呈现在公共互联网上。

NAT 的工作方式是，当一台拥有私有 IP 地址的企业计算机向企业网络外的公共 IP 地址发送数据包时，首先会进入 NAT 设备。NAT 在翻译表中记下数据包的源地址和目的地址。NAT 将数据包的源地址改为 NAT 设备面向公众的地址，并将数据包一起发送到外部目的地。当数据包回复时，NAT 将目的地址翻译成发起通信的计算机的私有 IP 地址。这样一来，一个公网 IP 地址可以代表多台私有地址的计算机。

### 谁在部署 IPv6？

运营商网络和互联网服务供应商是最早开始在其网络上部署 IPv6 的群体，其中移动网络处于领先地位。例如，T-Mobile USA 有超过 90% 的流量通过 IPv6，Verizon Wireless 紧随其后，占 82.25%。根据行业组织 [World Ipv6 Launch](http://www.worldipv6launch.org/measurements/) 的数据，Comcast 和 AT&T 的网络分别为 63% 和 65%。

主要网站则排在其后 —— World IPv6 Launch 称，目前 Alexa 前 1000 的网站中只有不到 30% 可以通过 IPv6 到达。

企业在部署方面比较落后，根据<ruby> 互联网协会 <rt>  Internet Society </rt></ruby>的[《2017年 IPv6 部署状况》报告](https://www.internetsociety.org/resources/doc/2017/state-of-ipv6-deployment-2017/)，只有不到四分之一的企业宣传其 IPv6 前缀。复杂性、成本和完成迁移所需时间都是他们给出的理由。此外，一些项目由于软件兼容性的问题而被推迟。例如，一份 [2017 年 1 月的报告](https://www.theregister.co.uk/2017/01/19/windows_10_bug_undercuts_ipv6_rollout/https://www.theregister.co.uk/2017/01/19/windows_10_bug_undercuts_ipv6_rollout/)称，Windows 10 中的一个 bug “破坏了微软在其西雅图总部推出纯 IPv6 网络的努力”。

### 何时会有更多部署？

互联网协会表示，IPv4 地址的价格将在 2018 年达到顶峰，然后在 IPv6 部署通过 50% 大关后，价格会下降。目前，[根据 Google](https://www.google.com/intl/en/ipv6/statistics.html)，全球的 IPv6 采用率为 20% 到 22%，但在美国约为 32%。

随着 IPv4 地址的价格开始下降，互联网协会建议企业出售现有的 IPv4 地址，以帮助资助其 IPv6 的部署。根据[一个发布在 GitHub 上的说明](https://gist.github.com/simonster/e22e50cd52b7dffcf5a4db2b8ea4cce0)，麻省理工学院已经这样做了。这所大学得出的结论是，其有 800 万个 IPv4 地址是“过剩”的，可以在不影响当前或未来需求的情况下出售，因为它还持有 20 个<ruby> 非亿级 <rt>  nonillion </rt></ruby> IPv6 地址。（非亿级地址是指数字 1 后面跟着 30 个零）。

此外，随着部署的增多，更多的公司将开始对 IPv4 地址的使用收费，而免费提供 IPv6 服务。[英国的 ISP Mythic Beasts](https://www.mythic-beasts.com/sales/ipv6) 表示，“IPv6 连接是标配”，而 “IPv4 连接是可选的额外服务”。

### IPv4 何时会被“关闭”？

在 2011 年至 2018 年期间，世界上大部分地区[“用完”了新的 IPv4 地址](https://ipv4.potaroo.net/) —— 但我们不会完全没有 IPv4 地址，因为 IPv4 地址会被出售和重新使用（如前所述），而剩余的地址将用于 IPv6 过渡。

目前还没有正式的 IPv4 关闭日期，所以人们不用担心有一天他们的互联网接入会突然消失。随着越来越多的网络过渡，越来越多的内容网站支持 IPv6，以及越来越多的终端用户为 IPv6 功能升级设备，世界将慢慢远离 IPv4。

### 为什么没有 IPv5？

曾经有一个 IPv5，也被称为<ruby> 互联网流协议 <rt>  Internet Stream Protocol </rt></ruby>，简称 ST。它被设计用于跨 IP 网络的面向连接的通信，目的是支持语音和视频。

它在这个任务上是成功的，并被实验性地使用。它的一个缺点是它的 32 位地址方案 —— 与 IPv4 使用的方案相同，从而影响了它的普及。因此，它存在着与 IPv4 相同的问题 —— 可用的 IP 地址数量有限。这导致了发展出了 IPv6 并和最终得到采用。尽管 IPv5 从未被公开采用，但它已经用掉了 IPv5 这个名字。

---

via: <https://www.networkworld.com/article/3254575/what-is-ipv6-and-why-aren-t-we-there-yet.html>

作者：[Keith Shaw](https://www.networkworld.com/author/Keith-Shaw/),[Josh Fruhlinger](https://www.networkworld.com/author/Josh-Fruhlinger/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[wxy](https://github.com/wxy) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


如何在 Ubuntu Linux 上禁用 IPv6

Summary: 想知道怎样在 Ubuntu 上禁用 IPv6 吗？我会在这篇文章中介绍一些方法，以及为什么你应该考虑这一选择

# 如何在 Ubuntu Linux 上禁用 IPv6

想知道怎样在 Ubuntu 上**禁用 IPv6** 吗？我会在这篇文章中介绍一些方法，以及为什么你应该考虑这一选择；以防改变主意，我也会提到如何**启用，或者说重新启用 IPv6**。

### 什么是 IPv6？为什么会想要禁用它？

<ruby>   互联网协议第 6 版  <rt>  Internet Protocol version 6 </rt></ruby>（IPv6）是互联网协议（IP）的最新版本。互联网协议是一种通信协议，它为网络上的计算机提供识别和定位系统，并在互联网上进行通信路由。IPv6 于 1998 年设计，以取代 IPv4 协议。

**IPv6** 意在提高安全性与性能的同时保证地址不被用尽；它可以在全球范围内为每台设备分配唯一的以 **128 位比特**存储的地址，而 IPv4 只使用了 32 位比特。

尽管 IPv6 的目标是取代 IPv4，但目前还有很长的路要走；互联网上只有不到 **30%** 的网站支持 IPv6（[这里](https://www.google.com/intl/en/ipv6/statistics.html) 是谷歌的统计），IPv6 有时也给 [一些应用带来问题](https://whatismyipaddress.com/ipv6-issues)。

由于 IPv6 使用全球（唯一分配的）路由地址，以及（仍然）有<ruby> 互联网服务供应商 <rt>  Internet Service Provider </rt></ruby>（ISP）不提供 IPv6 支持的事实，IPv6 这一功能在提供全球服务的<ruby>   虚拟私人网络  <rt>  Virtual Private Network </rt></ruby>（VPN）供应商的优先级列表中处于较低的位置，这样一来，他们就可以专注于对 VPN 用户最重要的事情：安全。

不想让自己暴露在各种威胁之下可能是另一个让你想在系统上禁用 IPv6 的原因。虽然 IPv6 本身比 IPv4 更安全，但我所指的风险是另一种性质上的。如果你不实际使用 IPv6 及其功能，那么[启用 IPv6 后，你会很容易受到各种攻击](https://www.internetsociety.org/blog/2015/01/ipv6-security-myth-1-im-not-running-ipv6-so-i-dont-have-to-worry/)，因而为黑客提供另一种可能的利用工具。

同样，只配置基本的网络规则是不够的；你必须像对 IPv4 一样，对调整 IPv6 的配置给予同样的关注，这可能会是一件相当麻烦的事情（维护也是）。并且随着 IPv6 而来的将会是一套不同于 IPv4 的问题（鉴于这个协议的年龄，许多问题已经可以在网上找到了），这又会使你的系统多了一层复杂性。

据观察，在某些情况下，禁用 IPv6 有助于提高 Ubuntu 的 WiFi 速度。

### 在 Ubuntu 上禁用 IPv6 [高级用户]

在本节中，我会详述如何在 Ubuntu 上禁用 IPv6 协议，请打开终端（默认快捷键：`CTRL+ALT+T`），让我们开始吧！

**注意：**接下来大部分输入终端的命令都需要 root 权限（`sudo`）。

>
> 警告！
>
>
> 如果你是一个普通 Linux 桌面用户，并且偏好稳定的工作系统，请避开本教程，接下来的部分是为那些知道自己在做什么以及为什么要这么做的用户准备的。
>
>
>

#### 1、使用 sysctl 禁用 IPv6

首先，可以执行以下命令来**检查** IPv6 是否已经启用：

```
ip a

```

如果启用了，你应该会看到一个 IPv6 地址（网卡的名字可能会与图中有所不同）

在教程《[在 Ubuntu 中重启网络](/article-10804-1.html)》（LCTT 译注：其实这篇文章并没有提到使用 sysctl 的方法……）中，你已经见过 `sysctl` 命令了，在这里我们也同样会用到它。要**禁用 IPv6**，只需要输入三条命令：

```
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1
sudo sysctl -w net.ipv6.conf.default.disable_ipv6=1
sudo sysctl -w net.ipv6.conf.lo.disable_ipv6=1

```

检查命令是否生效：

```
ip a

```

如果命令生效，你应该会发现 IPv6 的条目消失了：

然而这种方法只能**临时禁用 IPv6**，因此在下次系统启动的时候，IPv6 仍然会被启用。

（LCTT 译注：这里的临时禁用是指这次所做的改变直到此次关机之前都有效，因为相关的参数是存储在内存中的，可以改变值，但是在内存断电后就会丢失；这种意义上来讲，下文所述的两种方法都是临时的，只不过改变参数值的时机是在系统启动的早期，并且每次系统启动时都有应用而已。那么如何完成这种意义上的永久改变？答案是在编译内核的时候禁用相关功能，然后要后悔就只能重新编译内核了（悲）。）

一种让选项持续生效的方式是修改文件 `/etc/sysctl.conf`，在这里我用 `vim` 来编辑文件，不过你可以使用任何你想使用的编辑器，以及请确保你拥有**管理员权限**（用 `sudo`）：

将下面这几行（和之前使用的参数相同）加入到文件中：

```
net.ipv6.conf.all.disable_ipv6=1
net.ipv6.conf.default.disable_ipv6=1
net.ipv6.conf.lo.disable_ipv6=1

```

执行以下命令应用设置：

```
sudo sysctl -p

```

如果在重启之后 IPv6 仍然被启用了，而你还想继续这种方法的话，那么你必须（使用 root 权限）创建文件 `/etc/rc.local` 并加入以下内容：

```
#!/bin/bash
# /etc/rc.local

/etc/sysctl.d
/etc/init.d/procps restart

exit 0

```

接着使用 [chmod 命令](https://linuxhandbook.com/chmod-command/) 来更改文件权限，使其可执行：

```
sudo chmod 755 /etc/rc.local

```

这会让系统（在启动的时候）从之前编辑过的 sysctl 配置文件中读取内核参数。

#### 2、使用 GRUB 禁用 IPv6

另外一种方法是配置 **GRUB**，它会在系统启动时向内核传递参数。这样做需要编辑文件 `/etc/default/grub`（请确保拥有管理员权限）。

现在需要修改文件中分别以 `GRUB_CMDLINE_LINUX_DEFAULT` 和 `GRUB_CMDLINE_LINUX` 开头的两行来在启动时禁用 IPv6：

```
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash ipv6.disable=1"
GRUB_CMDLINE_LINUX="ipv6.disable=1"

```

（LCTT 译注：这里是指在上述两行内增加参数 `ipv6.disable=1`，不同的系统中这两行的默认值可能有所不同。）

保存文件，然后执行命令：

```
sudo update-grub

```

（LCTT 译注：该命令用以更新 GRUB 的配置文件，在没有 `update-grub` 命令的系统中需要使用 `sudo grub-mkconfig -o /boot/grub/grub.cfg` ）

设置会在重启后生效。

### 在 Ubuntu 上重新启用 IPv6

要想重新启用 IPv6，你需要撤销之前的所有修改。不过只是想临时启用 IPv6 的话，可以执行以下命令：

```
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=0
sudo sysctl -w net.ipv6.conf.default.disable_ipv6=0
sudo sysctl -w net.ipv6.conf.lo.disable_ipv6=0

```

否则想要持续启用的话，看看是否修改过 `/etc/sysctl.conf`，可以删除掉之前增加的部分，也可以将它们改为以下值（两种方法等效）：

```
net.ipv6.conf.all.disable_ipv6=0
net.ipv6.conf.default.disable_ipv6=0
net.ipv6.conf.lo.disable_ipv6=0

```

然后应用设置（可选）：

```
sudo sysctl -p

```

（LCTT 译注：这里可选的意思可能是如果之前临时启用了 IPv6 就没必要再重新加载配置文件了）

这样应该可以再次看到 IPv6 地址了：

另外，你也可以删除之前创建的文件 `/etc/rc.local`（可选）：

```
sudo rm /etc/rc.local

```

如果修改了文件 `/etc/default/grub`，回去删掉你所增加的参数：

```
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"
GRUB_CMDLINE_LINUX=""

```

然后更新 GRUB 配置文件：

```
sudo update-grub

```

### 尾声

在这篇文章中，我介绍了在 Linux 上**禁用 IPv6** 的方法，并简述了什么是 IPv6 以及可能想要禁用掉它的原因。

那么，这篇文章对你有用吗？你有禁用掉 IPv6 连接吗？让我们评论区见吧～

---

via: <https://itsfoss.com/disable-ipv6-ubuntu-linux/>

作者：[Sergiu](https://itsfoss.com/author/sergiu/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[rakino](https://github.com/rakino) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


编写项目文档时要问自己的 5 个问题

# 编写项目文档时要问自己的 5 个问题

>
> 使用有效沟通的一些基本原则可以帮助你创建与你的品牌一致的、编写良好、内容丰富的项目文档。
>
>
>

<img src="content_image/album_202010_06_223150omjnutjpml8inc9n.jpg" alt="" title="">

在开始实际撰写又一个开源项目的文档之前，甚至在采访专家之前，最好回答一些有关新文档的高级问题。

著名的传播理论家 Harold Lasswell 在他 1948 年的文章《<ruby> 社会中的传播结构和功能 <rt>  The Structure and Function of Communication in Society </rt></ruby>》中写道：

>
> （一种）描述沟通行为的方便方法是回答以下问题：
>
>
> * 谁
> * 说什么
> * 在哪个渠道
> * 对谁
> * 有什么效果？
>
>
>

作为一名技术交流者，你可以运用 Lasswell 的理论，回答关于你文档的类似问题，以更好地传达你的信息，达到预期的效果。

### 谁：谁是文档的所有者？

或者说，文档背后是什么公司？它想向受众传达什么品牌形象？这个问题的答案将极大地影响你的写作风格。公司可能有自己的风格指南，或者至少有正式的使命声明，在这种情况下，你应该从这开始。

如果公司刚刚起步，你可以向文件的主人提出上述问题。作为作者，将你为公司创造的声音和角色与你自己的世界观和信念结合起来是很重要的。这将使你的写作看起来更自然，而不像公司的行话。

### 说什么：文件类型是什么？

你需要传达什么信息？它是什么类型的文档：用户指南、API 参考、发布说明等？许多文档类型有模板或普遍认可的结构，这些结构为你提供一个开始的地方，并帮助确保包括所有必要的信息。

### 在哪个渠道：文档的格式是什么？

对于技术文档，沟通的渠道通常会告诉你文档的最终格式，也就是 PDF、HTML、文本文件等。这很可能也决定了你应该使用什么工具来编写你的文档。

### 对谁：目标受众是谁？

谁会阅读这份文档？他们的知识水平如何？他们的工作职责和主要挑战是什么？这些问题将帮助你确定你应该覆盖什么内容，是否应该应该涉及细节，是否可以使用特定的术语，等等。在某些情况下，这些问题的答案甚至可以影响你使用的语法的复杂性。

### 有什么效果：文档的目的是什么？

在这里，你应该定义这个文档要为它的潜在读者解决什么问题，或者它应该为他们回答什么问题。例如，你的文档的目的可以是教你的客户如何使用你的产品。

这时，你可以参考 [Divio](https://documentation.divio.com/) 建议的方法。根据这种方法，你可以根据文档的总体方向，将任何文档分为四种类型之一：学习、解决问题、理解或获取信息。

在这个阶段，另一个很好的问题是，这个文档要解决什么业务问题（例如，如何削减支持成本）。带着业务问题，你可能会看到你写作的一个重要角度。

### 总结

上面的问题旨在帮助你形成有效沟通的基础，并确保你的文件涵盖了所有应该涵盖的内容。你可以把它们分解成你自己的问题清单，并把它们放在身边，以便在你有文件要创建的时候使用。当你面对空白页无从着笔时，这份清单也可能会派上用场。希望它能激发你的灵感，帮助你产生想法。

---

via: <https://opensource.com/article/20/9/project-documentation>

作者：[Alexei Leontief](https://opensource.com/users/alexeileontief) 选题：[lujun9972](https://github.com/lujun9972) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


构建一个即时消息应用（六）：仅用于开发的登录

# 构建一个即时消息应用（六）：仅用于开发的登录

<img src="content_image/album_202010_07_101437garmhgi9aza9f9fz.jpg" alt="" title="">

本文是该系列的第六篇。

* [第一篇: 模式](/article-11396-1.html)
* [第二篇: OAuth](/article-11510-1.html)
* [第三篇: 对话](/article-12056-1.html)
* [第四篇: 消息](/article-12680-1.html)
* [第五篇: 实时消息](/article-12685-1.html)

我们已经实现了通过 GitHub 登录，但是如果想把玩一下这个 app，我们需要几个用户来测试它。在这篇文章中，我们将添加一个为任何用户提供登录的端点，只需提供用户名即可。该端点仅用于开发。

首先在 `main()` 函数中添加此路由。

```
router.HandleFunc("POST", "/api/login", requireJSON(login))

```

### 登录

此函数处理对 `/api/login` 的 POST 请求，其中 JSON body 只包含用户名，并以 JSON 格式返回通过认证的用户、令牌和过期日期。

```
func login(w http.ResponseWriter, r \*http.Request) {
    if origin.Hostname() != "localhost" {
        http.NotFound(w, r)
        return
    }

    var input struct {
        Username string `json:"username"`
    }
    if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    defer r.Body.Close()

    var user User
    if err := db.QueryRowContext(r.Context(), `
 SELECT id, avatar\_url
 FROM users
 WHERE username = $1
 `, input.Username).Scan(
        &user.ID,
        &user.AvatarURL,
    ); err == sql.ErrNoRows {
        http.Error(w, "User not found", http.StatusNotFound)
        return
    } else if err != nil {
        respondError(w, fmt.Errorf("could not query user: %v", err))
        return
    }

    user.Username = input.Username

    exp := time.Now().Add(jwtLifetime)
    token, err := issueToken(user.ID, exp)
    if err != nil {
        respondError(w, fmt.Errorf("could not create token: %v", err))
        return
    }

    respond(w, map[string]interface{}{
        "authUser":  user,
        "token":     token,
        "expiresAt": exp,
    }, http.StatusOK)
}

```

首先，它检查我们是否在本地主机上，或者响应为 `404 Not Found`。它解码主体跳过验证，因为这只是为了开发。然后在数据库中查询给定用户名的用户，如果没有，则返回 `404 NOT Found`。然后，它使用用户 ID 作为主题发布一个新的 JSON Web 令牌。

```
func issueToken(subject string, exp time.Time) (string, error) {
    token, err := jwtSigner.Encode(jwt.Claims{
        Subject:    subject,
        Expiration: json.Number(strconv.FormatInt(exp.Unix(), 10)),
    })
    if err != nil {
        return "", err
    }
    return string(token), nil
}

```

该函数执行的操作与 [前文](/article-11510-1.html) 相同。我只是将其移过来以重用代码。

创建令牌后，它将使用用户、令牌和到期日期进行响应。

### 种子用户

现在，你可以将要操作的用户添加到数据库中。

```
INSERT INTO users (id, username) VALUES
    (1, 'john'),
    (2, 'jane');

```

你可以将其保存到文件中，并通过管道将其传送到 Cockroach CLI。

```
cat seed_users.sql | cockroach sql --insecure -d messenger

```

---

就是这样。一旦将代码部署到生产环境并使用自己的域后，该登录功能将不可用。

本文也结束了所有的后端开发部分。

* [源代码](https://github.com/nicolasparada/go-messenger-demo)

---

via: <https://nicolasparada.netlify.com/posts/go-messenger-dev-login/>

作者：[Nicolás Parada](https://nicolasparada.netlify.com/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[gxlct008](https://github.com/gxlct008) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


使用 Flutter 创建 App

Summary: 使用流行的 Flutter 框架开始你的跨平台开发之旅。

使用流行的 Flutter 框架开始你的跨平台开发之旅。

# 使用 Flutter 创建 App

>
> 使用流行的 Flutter 框架开始你的跨平台开发之旅。
>
>
>

[Flutter](https://flutter.dev/) 是一个深受全球移动开发者欢迎的项目。该框架有一个庞大的、友好的爱好者社区，随着 Flutter 帮助程序员将他们的项目带入移动领域，这个社区还在继续增长。

本教程旨在帮助你开始使用 Flutter 进行移动开发。阅读之后，你将了解如何快速安装和设置框架，以便开始为智能手机、平板电脑和其他平台开发。

本操作指南假定你已在计算机上安装了 [Android Studio](https://developer.android.com/studio)，并且具有一定的使用经验。

### 什么是 Flutter ？

Flutter 使得开发人员能够为多个平台构建应用程序，包括：

* Android
* iOS
* Web（测试版）
* macOS（正在开发中）
* Linux（正在开发中）

对 macOS 和 Linux 的支持还处于早期开发阶段，而 Web 支持预计很快就会发布。这意味着你可以立即试用其功能（如下所述）。

### 安装 Flutter

我使用的是 Ubuntu 18.04，但其他 Linux 发行版安装过程与之类似，比如 Arch 或 Mint。

#### 使用 snapd 安装

要使用 [Snapd](https://snapcraft.io/docs/getting-started) 在 Ubuntu 或类似发行版上安装 Flutter，请在终端中输入以下内容：

```
$ sudo snap install flutter --classic

$ sudo snap install flutter –classic
flutter 0+git.142868f from flutter Team/ installed

```

然后使用 `flutter` 命令启动它。 首次启动时，该框架会下载到你的计算机上：

```
$ flutter
Initializing Flutter
Downloading https://storage.googleapis.com/flutter_infra[...]

```

下载完成后，你会看到一条消息，告诉你 Flutter 已初始化：

#### 手动安装

如果你没有安装 Snapd，或者你的发行版不是 Ubuntu，那么安装过程会略有不同。在这种情况下，请[下载](https://flutter.dev/docs/get-started/install/linux) 为你的操作系统推荐的 Flutter 版本。

然后将其解压缩到你的主目录。

在你喜欢的文本编辑器中打开主目录中的 `.bashrc` 文件（如果你使用 [Z shell](https://opensource.com/article/19/9/getting-started-zsh)，则打开 `.zshc`）。因为它是隐藏文件，所以你必须首先在文件管理器中启用显示隐藏文件，或者使用以下命令从终端打开它：

```
$ gedit ~/.bashrc &

```

将以下行添加到文件末尾：

```
export PATH="$PATH:~/flutter/bin"

```

保存并关闭文件。 请记住，如果在你的主目录之外的其他位置解压 Flutter，则 [Flutter SDK 的路径](https://opensource.com/article/17/6/set-path-linux) 将有所不同。

关闭你的终端，然后再次打开，以便加载新配置。 或者，你可以通过以下命令使配置立即生效：

```
$ . ~/.bashrc

```

如果你没有看到错误，那说明一切都是正常的。

这种安装方法比使用 `snap` 命令稍微困难一些，但是它非常通用，可以让你在几乎所有的发行版上安装该框架。

#### 检查安装结果

要检查安装结果，请在终端中输入以下内容：

```
flutter doctor -v

```

你将看到有关已安装组件的信息。 如果看到错误，请不要担心。 你尚未安装任何用于 Flutter SDK 的 IDE 插件。

### 安装 IDE 插件

你应该在你的 [集成开发环境（IDE）](https://www.redhat.com/en/topics/middleware/what-is-ide) 中安装插件，以帮助它与 Flutter SDK 接口、与设备交互并构建代码。

Flutter 开发中常用的三个主要 IDE 工具是 IntelliJ IDEA（社区版）、Android Studio 和 VS Code（或 [VSCodium](https://opensource.com/article/20/6/open-source-alternatives-vs-code)）。我在本教程中使用的是 Android Studio，但步骤与它们在 IntelliJ Idea（社区版）上的工作方式相似，因为它们构建在相同的平台上。

首先，启动 Android Studio。打开 “Settings”，进入 “Plugins” 窗格，选择 “Marketplace” 选项卡。在搜索行中输入 “Flutter”，然后单击 “Install”。

你可能会看到一个安装 “Dart” 插件的选项；同意它。如果看不到 Dart 选项，请通过重复上述步骤手动安装它。我还建议使用 “Rainbow Brackets” 插件，它可以让代码导航更简单。

就这样！你已经安装了所需的所有插件。你可以在终端中输入一个熟悉的命令进行检查：

```
flutter doctor -v

```

### 构建你的 “Hello World” 应用程序

要启动新项目，请创建一个 Flutter 项目：

1、选择 “New -> New Flutter project”。

2、在窗口中，选择所需的项目类型。 在这种情况下，你需要选择 “Flutter Application”。

3、命名你的项目为 `hello_world`。 请注意，你应该使用合并的名称，因此请使用下划线而不是空格。 你可能还需要指定 SDK 的路径。

4、输入软件包名称。

你已经创建了一个项目！现在，你可以在设备上或使用模拟器启动它。

选择你想要的设备，然后按 “Run”。稍后，你将看到结果。

现在你可以在一个 [中间项目](https://opensource.com/article/18/6/flutter) 上开始工作了。

### 尝试 Flutter for web

在安装 Flutter 的 Web 组件之前，你应该知道 Flutter 目前对 Web 应用程序的支持还很原始。 因此，将其用于复杂的项目并不是一个好主意。

默认情况下，基本 SDK 中不启用 “Flutter for web”。 要打开它，请转到 beta 通道。 为此，请在终端中输入以下命令：

```
flutter channel beta

```

接下来，使用以下命令根据 beta 分支升级 Flutter：

```
flutter upgrade

```

要使 “Flutter for web” 工作，请输入：

```
flutter config --enable-web

```

重新启动 IDE；这有助于 Android Studio 索引新的 IDE 并重新加载设备列表。你应该会看到几个新设备：

选择 “Chrome” 会在浏览器中启动一个应用程序， “Web Server” 会提供指向你的 Web 应用程序的链接，你可以在任何浏览器中打开它。

不过，现在还不是急于开发的时候，因为你当前的项目不支持 Web。要改进它，请打开项目根目录下的终端，然后输入：

```
flutter create

```

此命令重新创建项目，并添加 Web 支持。 现有代码不会被删除。

请注意，目录树已更改，现在有了一个 `web` 目录：

现在你可以开始工作了。 选择 “Chrome”，然后按 “Run”。 稍后，你会看到带有应用程序的浏览器窗口。

恭喜你！ 你刚刚为浏览器启动了一个项目，并且可以像其他任何网站一样继续使用它。

所有这些都来自同一代码库，因为 Flutter 使得几乎无需更改就可以为移动平台和 Web 编写代码。

### 用 Flutter 做更多的事情

Flutter 是用于移动开发的强大工具，而且它也是迈向跨平台开发的重要一步。 了解它，使用它，并将你的应用程序交付到所有平台！

---

via: <https://opensource.com/article/20/9/mobile-app-flutter>

作者：[Vitaly Kuprenko](https://opensource.com/users/kooper) 选题：[lujun9972](https://github.com/lujun9972) 译者：[gxlct008](https://github.com/gxlct008) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出