DuetG Blog

Keep It Simple, Stupid

分类: 开小灶

  • Mastodon全文搜索中文分词优化指南

    Mastodon全文搜索中文分词优化指南

    本文以Docker版Mastodon实例为主要环境,解决开启全文搜索后中文无法正常搜索的问题。如果有非Docker环境的实例在开启全文搜索后建议参考官方文档中的配置方法来优化中文分词。下文中提到的很多问题是仅限Docker版才有,请勿搞混。

    下面所提到的都是在Mastodon当下最新版本(v4.5.5)的基础上所做的操作,后续版本如有变动,请以官方文档为准。

    0. 启用全文搜索

    Mastodon官方文档不是针对Docker环境书写的,这就对那些小型单节点的Docker版Mastodon实例用户造成了一定的困扰。目前使用Docker架设Mastodon小型单节点实例的最快的方法是使用官方GitHub站点上的docker-compose.yml文件。考虑对主机资源的占用,docker-compose.yml中默认并不开启全文搜索,即代码中第28-57行默认是注释掉的。

    ...
      # es:
      #   restart: always
      #   image: docker.elastic.co/elasticsearch/elasticsearch:7.17.29
      #   environment:
      #     - "ES_JAVA_OPTS=-Xms512m -Xmx512m -Des.enforce.bootstrap.checks=true"
      #     - "xpack.license.self_generated.type=basic"
      #     - "xpack.security.enabled=false"
      #     - "xpack.watcher.enabled=false"
      #     - "xpack.graph.enabled=false"
      #     - "xpack.ml.enabled=false"
      #     - "bootstrap.memory_lock=true"
      #     - "cluster.name=es-mastodon"
      #     - "discovery.type=single-node"
      #     - "thread_pool.write.queue_size=1000"
      #   networks:
      #      - external_network
      #      - internal_network
      #   healthcheck:
      #      test: ["CMD-SHELL", "curl --silent --fail localhost:9200/_cluster/health || exit 1"]
      #   volumes:
      #      - ./elasticsearch:/usr/share/elasticsearch/data
      #   ulimits:
      #     memlock:
      #       soft: -1
      #       hard: -1
      #     nofile:
      #       soft: 65536
      #       hard: 65536
      #   ports:
      #     - '127.0.0.1:9200:9200'
    ...

    假如之前把GitHub上给的.env.production.sample中的内容复制到了Mastodon配置文件.env.production中,也就是Elasticsearch (optional)字段保持了原样。此时我们只需要在docker-compose.yml中的上面这段代码的注释(即#号)去掉,然后再次上线整个docker compose,待Elasticsearch 7.17.29的镜像拉取成功后,你会发现Elasticsearch容器并没有像预先计划的那样启动,这是由于权限不足的问题。我们需要先用 docker compose down 将整个docker compose下线,然后针对映射到主机本地生成的文件夹进行权限调整:

    chown -R 1000:1000 ./elasticsearch

    之后再用 docker compose up -d 上线整个docker compose,全文搜索就开启了。

    1. 安装中文分词和简繁转换插件

    然而此时的全文搜索并没有针对中文进行优化,所以会导致搜中文时需要自行使用引号把每个词组划分开,否则Mastodon默认会以每个单字为搜索单位进行搜索。搜索结果自然也是杂乱无章的。想要解决这个问题,Mastodon官方文档给出中文搜索的优化方案。只不过这个方案是针对非Docker版节点的,我们不能直接拿来就用。

    首先,官方文档介绍的要给Elasticsearch安装两个插件:

    因为docker-compose.yml的缘故,docker compose每次下线的时候容器都会被删除掉,所以即便是进入Elasticsearch的容器安装了插件并重启容器完成插件安装,一旦使用docker compose down,则安装好插件的容器就会被删除,前功尽弃。

    这时我们需要在Elasticsearch的官方文档中寻找解决方案。文档中的一篇文章详细介绍了在使用官方Docker镜像时,可以通过配置文件的方式来让Elasticsearch自动安装插件的方法。接下来我们就如法炮制:

    # 首先我们在工作目录中建立一个插件相关的目录并进入
    mkdir ./elasticsearch-plugins
    cd elasticsearch-plugins
    
    # 在该目录中我们创建一个配置文件,并把下面区块里的内容粘贴进去,这里我使用vim来创建配置文件
    vim elasticsearch-plugins.yml
    plugins:
      - id: analysis-ik
        location: file:///usr/share/elasticsearch/local-packages/elasticsearch-analysis-ik-7.17.29.zip
      - id: analysis-stconvert
        location: file:///usr/share/elasticsearch/local-packages/elasticsearch-analysis-stconvert-7.17.29.zip

    这里你可以看到我使用了 file:// 协议,没错,接下来我们把插件下载到本地,这样就避免了每次新建容器的时候都要从网上下载的延迟和风险。

    # 在elasticsearch-plugins中新建一个目录用来存放插件包并进入
    mkdir ./local-packages
    cd local-packages
    
    # 在local-packages目录中下载匹配Elasticsearch版本(v7.17.29)的插件
    wget https://release.infinilabs.com/analysis-ik/stable/elasticsearch-analysis-ik-7.17.29.zip
    wget https://release.infinilabs.com/analysis-stconvert/stable/elasticsearch-analysis-stconvert-7.17.29.zip

    接下来我们要返回主工作目录去修改docker-compose.yml,将elasticsearch-plugins.yml文件和local-packages目录映射进Elasticsearch容器中去,并且针对Elasticsearch 7.x Docker镜像的一个bug进行修正。

    首先我们先完成文件和目录的映射,我们在es区块下的volumes字段增加新的映射

    ...
       healthcheck:
          test: ["CMD-SHELL", "curl --silent --fail localhost:9200/_cluster/health || exit 1"]
       volumes:
          - ./elasticsearch:/usr/share/elasticsearch/data
          # 新增下两行
          - ./elasticsearch-plugins/elasticsearch-plugins.yml:/usr/share/elasticsearch/config/elasticsearch-plugins.yml
          - ./elasticsearch-plugins/local-packages:/usr/share/elasticsearch/local-packages
          # 新增完成
       ulimits:
         memlock:
           soft: -1
           hard: -1
    ...

    接下来就要说一下刚才提到的那个bug,GitHub上有这个Issue。简而言之,当通过elasticsearch-plugins.yml来为Docker容器安装插件的时候,Elasticsearch会自动在容器中 /usr/share/elasticsearch/plugins/ 目录下新建一个名为 .elasticsearch-plugins.yml.cache 的缓存文件来记录已经安装的插件。当容器再次重启的时候,Elasticsearch 7.x版本则会扫描到这个缓存文件,并把它当成一个插件来执行。显而易见是无法执行的,于是就报错并且不停尝试重启,最终启动失败。

    解决方法是在docker-compose.yml中增加启动时先删除该缓存文件的选项,让每次容器上线都是全新安装插件的状态。由于我们已经把插件包下载到本地,所以实际上并没有多少时间上的影响。代码如下:

    ...
      es:
        restart: always
        image: docker.elastic.co/elasticsearch/elasticsearch:7.17.29
        # 新增下一行
        entrypoint: ["/bin/tini", "--", "bash", "-c", "rm -f /usr/share/elasticsearch/plugins/.elasticsearch-plugins.yml.cache && /usr/local/bin/docker-entrypoint.sh eswrapper"]
        # 新增完成
        environment:
          - "ES_JAVA_OPTS=-Xms512m -Xmx512m -Des.enforce.bootstrap.checks=true"
          - "xpack.license.self_generated.type=basic"
          - "xpack.security.enabled=false"
          - "xpack.watcher.enabled=false"
          - "xpack.graph.enabled=false"
          - "xpack.ml.enabled=false"
          - "bootstrap.memory_lock=true"
          - "cluster.name=es-mastodon"
          - "discovery.type=single-node"
          - "thread_pool.write.queue_size=1000"
    ...

    新增这行命令是通过docker inspect对elasticsearch:7.17.29镜像进行检查,获取它的entrypoint指令,然后再在该指令上增加了删除.elasticsearch-plugins.yml.cache的指令后构建了新的entrypoint,以此覆盖镜像原本的entrypoint指令。

    最后回到主目录调整完 ./elasticsearch-plugins 目录的权限后

    chown -R 1000:1000 ./elasticsearch-plugins

    至此插件安装完成。

    2. 修改索引分词器选项以及增加简繁转换过滤器

    根据官方文档,我们需要修改web容器中 app/chewy/ 目录下所有以 _index.rb 结尾的文件。我们仍旧利用Docker的映射功能,把修改好的文件映射到容器中的位置,来覆盖镜像原有的设置。首先我们要把这个目录拷贝出来。假设你的web容器的名字是 live-web-1,下面的指令可以按照实际容器名来修改。

    # 假设当前为主工作目录,web容器名为live-web-1
    docker cp -a live-web-1:/opt/mastodon/app/chewy ./elasticsearch-plugins/

    这样一来elasticsearch-plugins目录下又多了一个chewy目录,进入该目录后能够看到五个以 _index.rb 结尾的文件以及一个名为concerns的目录。concerns目录及其内容不在修改范围内,我们主要查看的是剩下的这些以 _index.rb 结尾的文件。

    由于五个文件内容各有不同(其中instances_index.rb一般不用修改),所以接下来我们只讲修改方法。使用vim打开每个文件,查找 tokenizer: 字段(一般都有好几个),然后将其后面带有引号中的值(一般是standard,但也有其他内容)全部修改为ik_max_word,即:

    ...
    tokenizer: 'ik_max_word'
    ...

    然后再在每个文件中寻找包含 content: 字段的 analyzer: 字段。 然后在 filter: 和 analyzer: 之间插入

      char_filter: {
        tsconvert: {
          type: 'stconvert',
          keep_both: false,
          delimiter: '#',
          convert_type: 't2s',
        },
      },

    下面举个例子

    ...
      settings index: index_preset(refresh_interval: '30s', number_of_shards: 5), analysis: {
        filter: {
          english_stop: {
            type: 'stop',
            stopwords: '_english_',
          },
    
          english_stemmer: {
            type: 'stemmer',
            language: 'english',
          },
    
          english_possessive_stemmer: {
            type: 'stemmer',
            language: 'possessive_english',
          },
        },
    
        # 此段为新增
        char_filter: {
          tsconvert: {
            type: 'stconvert',
            keep_both: false,
            delimiter: '#',
            convert_type: 't2s',
          },
        },
        # 新增完成
    
        analyzer: {
          verbatim: {
            tokenizer: 'ik_max_word',
            filter: %w(lowercase),
          },
    ...

    注意tags_index.rb文件中本身没有 filter: 部分,请直接插在 analyzer: 部分之前即可。

    再在每个 analyzer: 中 content: 下添加 char_filter: %w(tsconvert), 来增加使用该过滤器,如

    ...
        char_filter: {
          tsconvert: {
            type: 'stconvert',
            keep_both: false,
            delimiter: '#',
            convert_type: 't2s',
          },
        },
    
        analyzer: {
          verbatim: {
            tokenizer: 'ik_max_word',
            filter: %w(lowercase),
          },
    
          content: {
            tokenizer: 'ik_max_word',
            filter: %w(
              lowercase
              asciifolding
              cjk_width
              elision
              english_possessive_stemmer
              english_stop
              english_stemmer
            ),
            # 新增下一行
            char_filter: %w(tsconvert),
            # 新增完成
          },
    
          hashtag: {
            tokenizer: 'ik_max_word',
    ...

    完成所有文件修改后返回主工作目录,重新调整 ./elasticsearch-plugins 目录的权限

    chown -R 1000:1000 ./elasticsearch-plugins

    然后再修改docker-compose.yml中web区块的volumes映射

    ...
        healthcheck:
          # prettier-ignore
          test: ['CMD-SHELL',"curl -s --noproxy localhost localhost:3000/health | grep -q 'OK' || exit 1"]
        ports:
          - '127.0.0.1:3000:3000'
        depends_on:
          - db
          - redis
          - es
        volumes:
          - ./public/system:/mastodon/public/system
          # 新增下一行
          - ./elasticsearch-plugins/chewy:/opt/mastodon/app/chewy
          # 新增完成
    ...

    3. 重建索引

    到这里所有需要调整的代码就算都完成了,接下来就是重建索引了。先用docker compose up -d来让所有容器都启动。然后用docker logs指令查看Elasticsearch容器是否已经正常启动。最后使用这条指令来重建索引(假设你的web容器名为live-web-1):

    docker exec -it live-web-1 tootctl search deploy --reset-chewy

    索引重建需要一段时间,待重建完成后用浏览器前往网页界面,在搜索框中尝试搜索中文,会发现显示的结果已经正常的分词后的搜索结果。

    完

    Fediverse Reactions
  • 给WordPress双重身份验证增加应用程序密码支持

    给WordPress双重身份验证增加应用程序密码支持

    2020年底发布的Wordpress5.6增加了应用程序密码(Application Passwords)功能,这个功能是建立在整合Application Passwords插件代码的基础上的。

    事实上Wordpress官方GitHub账号上还有一款双重身份验证插件,也就是Two-Factor插件。尽管在Wordpress.org的插件库里,这款插件的作者既没有写作官方商业插件的Automattic,也没写作开源插件的WordPress Contributors,而是写为Plugin Contributors。但鉴于它的代码在官方GitHub账号上托管的身份,它无疑算是官方的两步验证插件。只是这款插件已经发布了这么久,并没有见官方像应用程序密码一样把它的代码整合进核心代码。也不知道是不是为了给其他第三方商业两步验证插件留条生路。

    本着简单免费的原则,我还是选择了官方身份的这款免费插件。不过用了之后发现了些小问题——这款插件直接对已经开启了两步验证的账号禁用了API登录的请求。这导致我的IFTTT没有办法再和Wordpress互动。这是Two-Factor默认的设置,倒也不是bug,只不过它并没有给一个能处理的用户界面来修改。

    最终的解决方案是Application Passwords的作者在GitHub上给出来的,不过也只是一段代码,而非是增加一个用户界面选项。

    add_filter( 'two_factor_user_api_login_enable', function( $enable ) {
        // Allow API login when using an application password even with 2fa enabled.
        if ( did_action( 'application_password_did_authenticate' ) ) {
            return true;
        }
    
        return $enable;
    } );

    在Wordpress.org上插件库里,Application Passwords的介绍中也有这段代码的描述,只不过任何人都没有将它做成一个用户界面选项。然而Application Passwords的代码已经整合进了Wordpress核心代码里,以后恐怕也不太有可能看到有这个选项了,毕竟它本来就是Two-Factor的功能。

    把这段代码粘到主题的functions.php里就可以了。不过如果嫌麻烦,我也做了一个简单的插件,直接下载安装激活就可以了(本身就还是这段代码)。

  • 在家部署MinIO实现S3自由

    在家部署MinIO实现S3自由

    这是一篇与随后准备写的部署Mastodon实例相关的文章。

    S3是Simple Storage Service的缩写,是亚马逊提供的一种云存储模式,目前已是一种主流的云存储,大多数前端服务都支持S3接入,大多数的商业云存储服务也都兼容S3。

    商业云存储目前大多数都是付费模式,个别限时或限量的免费体验版整体来说使用起来都不很自由。

    借助开源软件的免费优势,我们今天来尝试在家庭服务器上部署自托管的MinIO对象存储服务。MinIO本身是兼容S3的,作为一种低成本解决方案,一旦部署好就可以基本替代商业云存储。要注意的是,低成本不代表无成本,基本替代也很难成为完美替代。

    首先我们家中需要一台服务器(或者是服务器上的虚拟机)和一个域名。另外家庭宽带也需要公网IP,宽带上下行速度会直接影响文件传输速度。如果没有公网IP想用frp穿透的话,其数据传输速度自然会更加受影响,所以建议要有公网IP。后面会以安装好Docker的Debian/Ubuntu系统举例,当然你要用Podman也没问题。

    先使用路由器的动态DNS(DDNS)功能把家庭宽带的公网IP自动更新到域名托管的DNS服务。然后再用路由器防火墙的端口映射功能把WAN口的9000端口映射到你服务器的9000端口。

    然后再服务器里建立工作文件夹,并生成MinIO的配置文件

    mkdir minio
    cd minio
    vim minioconfig

    将如下内容按实际修改后粘贴进去。MinIO更多的环境变量可以参见这里。

    # MINIO_ROOT_USER 和 MINIO_ROOT_PASSWORD 设置 MinIO 服务器的 root 帐户。 
    # 该用户拥有对部署中的任何资源执行 S3 和管理 API 操作的不受限制的权限。 
    # 如将其备注掉不设置则使用默认值“minioadmin:minioadmin”。 
    # MinIO 建议设置非默认值作为最佳实践,无论环境如何
    
    MINIO_ROOT_USER=myminioadmin
    MINIO_ROOT_PASSWORD=minio-secret-key-change-me
    
    # MINIO_VOLUMES 设置用于 MinIO 服务器的存储卷或路径。
    
    MINIO_VOLUMES="/mnt/data"
    
    # MINIO_SERVER_URL 设置与 MinIO 服务器一起使用的本地计算机的主机名 
    # MinIO 假设您的网络控制界面可以正确将此主机名解析到本地计算机 
    # 取消注释以下行,并将该值替换为本地计算机的正确主机名和 MinIO 服务器的端口(默认为 9000)。
    # 如https://你的域名:9000
    
    #MINIO_SERVER_URL="https://minio.example.net:9000"

    填入之后保存退出vim,之后来准备Docker Compose文件

    vim docker-compose.yml

    我们部署一个单节点单驱动的实例,进入编辑界面后把下面的内容按实际修改后填入

    version: '2'
    
    services:
      minio:
        container_name: minio
        command: server --console-address ":9090"
        environment:
          - MINIO_CONFIG_ENV_FILE=/etc/config.env
        image: quay.io/minio/minio:latest
        volumes:
          - ./data:/mnt/data
          - ./minioconfig:/etc/config.env
        restart: always
        ports:
          - "9001:9000"
          - "9090:9090"

    这里设定工作文件夹下的data文件夹是整个存储的目录,你可以按需求把第11行冒号前面的./data修改成你想要的目录。或者你可以在工作目录下建立一个名为data的链接到你想要的目标目录。也可以用nfs协议把内网其他位置的硬盘目录映射到这个data上,总之按你的需要来处理存储位置的设置。同时为了后面使用NGINX来实现反向代理端口,容器的9000端口先映射到主机的9001端口了。

    保存退出后就可以下载镜像生成容器了

    docker compose up -d

    容器顺利生成并启动后,我们来配置NGINX,首先设置站点配置文件

    vim /etc/nginx/sites-available/minio

    将下面的内容贴入编辑界面

    server {
            listen 9000 ssl default_server;
            listen [::]:9000 ssl default_server;
    
            include snippets/snakeoil.conf;
    
            ssl_session_cache shared:le_nginx_SSL:10m;
            ssl_session_timeout 1440m;
            ssl_session_tickets off;
    
            ssl_protocols TLSv1.2 TLSv1.3;
            ssl_prefer_server_ciphers off;
    
            ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
    
            server_name _;
    
            # To allow special characters in headers
            ignore_invalid_headers off;
            # Allow any size file to be uploaded.
            # Set to a value such as 1000m; to restrict file size to a specific value
            client_max_body_size 1000m;
            # To disable buffering
            proxy_buffering off;
            proxy_request_buffering off;
    
            location / {
                    # First attempt to serve request as file, then
                    # as directory, then fall back to displaying a 404.
                    # try_files $uri $uri/ =404;
                    proxy_set_header Host $http_host;
                    proxy_set_header X-Real-IP $remote_addr;
                    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                    proxy_set_header X-Forwarded-Proto $scheme;
    
                    proxy_connect_timeout 300;
                    # Default is HTTP/1, keepalive is only enabled in HTTP/1.1
                    proxy_http_version 1.1;
                    proxy_set_header Connection "";
                    chunked_transfer_encoding off;
    
                    proxy_pass http://127.0.0.1:9001;
            }
    }

    这里我们假定了证书的储存位置是/etc/nginx/snippets/snakeoil.conf写的

    ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
    ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;

    如果你希望是别的位置请修改相应的内容。保存退出后使用ln命令将其链接到enabled文件夹,并删掉其中的default配置

    ln -s /etc/nginx/sites-available/minio /etc/nginx/sites-enabled/
    rm /etc/nginx/sites-enabled/default

    然后我们使用acme.sh的DNS API功能来申请证书,使用DNS API申请证书不需要80端口,非常适合国内家庭宽带申请证书,而且acme.sh也支持大量的DNS服务商。具体的申请方法可参见这篇文章的前半部分。

    证书设置完毕后就可以重启NGINX了,也可以先用nginx -t来检查一下配置是否正确。这里我只把MinIO的服务端口9000映射到了公网,而网页控制台的9090端口依然还在内网,从而确保服务的安全。如果你需要在外网访问的话可以考虑使用Tailscale之类的服务来远程访问这台主机的端口。

    最后我们用这台服务器的内网IP加9090端口在浏览器上就可以访问MinIO的网页控制台,使用minioconfig中设置的用户名和密码登录即可。

    完

  • 小米盒子3c刷CoreELEC运行Home Assistant Container

    小米盒子3c刷CoreELEC运行Home Assistant Container

    这是一个穷人乐,如果你像我一样手头上正好有一个小米盒子3c,一条公对公USB数据线,再加上拆机棒、镊子等工具,可以照着下面折腾一下。但如果你只是为了有一个好用的Home Assistant,建议花300左右购入一个Orange Pi 3 LTS自行制作Home Assistant Supervised,或者700多块直接买一个Home Assistant Green,就不要瞎折腾了,更不要为这个专门去收个二手盒子。因为Home Assistant Container只是一个基本的Home Assistant(仅比Home Assistant Core强些),不能安装加载项(Add-ons),更不能安装社区商店(Home Assistant Community Store),可用性以及可玩性都不高。

    Update:今天才知道Home Assistant所有版本都可以安装HACS,被官网那张图骗了好几年……

    如果我上面说的还算清楚,并且你已经看到这里了,那就一起来穷人乐吧。

    刷机包的相关信息和教程在国内是被作者发到了恩山论坛上,时间是22年5月份。文件和源代码托管在全球最大同性交友网站GitHub上,还请自行前去下载。如果没有游戏需求,还请下载9.2-mibox3-1.4,在这个版本仅有CoreELEC。论坛上作者的教程写的还算详尽,请直接参考论坛上的帖子来刷机,这里我主要讲刷机过程中和刷机后的一些问题。至于刷机软件Amlogic USB Burning Tool,作者本人说用的是v2.0.8,其他的版本我也试了几个,结果都不行,但v2.0.8一次成功,所以建议搜索下载这个版本安装。

    如果你没有公对公USB数据线,也可以找两根不要的数据线剪断重接。线准备好了之后,就可以用拆机棒拆机了。这里有篇小米盒子3s的拆机文章,在外壳上和3c基本上是一样的,可以参考一下。经验是要从后往前拆,一旦后面的卡口撬开了,剩下的用拆机片沿着接缝就能划开。不过一开始是比较紧的,不是很好拆。

    拆机完成后就是刷机了,这里我再具体说明一下需要短接的两个测试点是在屏蔽罩的一角和螺丝中间的两个铜测试点,而不是另一边并列三个测试点其中的两个。

    找作者在帖子中所说的步骤准备固件,刷机软件,并把设备与电脑连线等等。软件请务必确保在右侧仅选中“擦除flash”以及下拉菜单里选择“擦除所有”。用左手拿镊子保持短接,右手把电源打开,听到电脑响起硬件插入的响声后立刻取消短接,然后静等烧录完毕。如果中间有问题烧录中止了,请全部断开然后从头重新操作一遍。

    烧录完成后给盒子断电,拔掉数据线。接上HDMI显示器以及键盘或鼠标,插上电源重新开机,就可以看到CoreELEC的启动画面了。

    CoreELEC本身就是一个基于Kodi的极小化Linux系统,所以进去之后修改中文什么的在网上搜一下就可以找得到教程。如果首次登录的时候设置Wi-Fi出错跳过了这个步骤,可以到设置菜单最下面的CoreELEC菜单里重新录入Wi-Fi密码,蓝牙遥控器也需要在这个菜单下重新连接。遥控器进入配对模式是需要按住Home键和菜单键两个不松手,稍等片刻就进入配对模式。如果没有切换成中文就去连接遥控器,会在蓝牙上看到一个名称都是方块的设备,这个就是遥控器没错了。

    如果语言列表里没有中文,需要到插件菜单里下载中文,但在切换中文前请务必把皮肤的字体改为基于Arial,否则整个UI也会变成方块。

    在插件中找到CoreELEC的官方源,在服务程序里找到Docker安装好,并在设置里开启ssh。然后通过ssh进入CoreELEC的命令行,使用以下命令来安装并启动Home Assistant Container。默认用户名是root,默认密码是coreelec。

    docker run --restart always -d --name homeassistant -v ~/homeassistant/config:/config -e TZ=Asia/Shanghai --net=host ghcr.io/home-assistant/home-assistant:stable

    待镜像下载完毕容器跑起来之后,使用你的盒子的IP,端口8123,就可以访问Home Assistant。

    最后说一下遥控器。刚刷好的CoreELEC,把遥控器的连牙蓝上了之后,遥控器的电源键会一键关闭盒子上的系统(并不会彻底关闭电源,也不能开机)。但这种设定很容易带来误操作的麻烦。在网上搜了搜,找到了重新定义keymap的解决方案,一个CoreELEC官方论坛上的帖子。根据官方开发人员的回复,在电脑资源管理器的地址栏输入

     \\你的小米盒子ip\Userdata\keymaps

    可能会需要你输入用户名和密码,由于之前没有修改过密码,所以用户名是root,密码是coreelec。

    打开文件夹后把帖子里的keymaps.xml下载并上传到这个文件夹里,重启以下系统,之后遥控器的电源键就只能触发电源菜单而不会直接关机了。

    完

  • 使用NTFS-3G在macOS上免费读写NTFS驱动器

    使用NTFS-3G在macOS上免费读写NTFS驱动器

    这是个救穷不救急的方法,如果着急用,建议还是花139块(或19.95刀)去买个Paragon NTFS for Mac。

    出于个人原因,突然需要从一台老iMac里拷贝出大量的文件,这让我本来就捉襟见肘的储存空间更加的雪上加霜。万般无奈下从京东买了块致态的500GB固态硬盘,把以前笔记本里的1TB机械硬盘换了出来。由于硬盘上本就还有数据,不能格式化,所以就只能在NTFS的文件系统下连接iMac了。

    众所周知macOS对NTFS是只读的,在“因为是个一次性的小需求所以不想花钱买软件”的指导思想下,我只得选择安装商业软件,并在试用期内把问题搞定。然而因为拖延症以及我对“数据读写也要耗时”这个事情从没有像这次一样直观的体验,令我在这台iMac上陆续装了三款免费试用的NTFS装载软件,并逐一都过了期。

    然而我的活儿却还没干完。

    思前想后,决定找一个完全免费的解决方案,通过搜索找到了NTFS-3G这样一款开源跨平台的NTFS驱动。不过想要在macOS上使用,还需要借助macFUSE才可以。下面写的基本上来自于macFUSE官方Wiki上有关NTFS-3G的指导。如果英语不错的话,也可以直接去看原文。

    首先由于需要编译,所以你需要在Mac上安装Xcode,确保Command Line Tools安装到系统。如果没有的话可以在终端里使用这条命令:

    sudo xcode-select --install

    接下来需要安装Homebrew来在终端里实现软件包的安装和管理。

    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

    较低版本的macOS是无法通过官网的 .pkg 包完成安装的,会验证你的系统版本,所以用命令行安装是比较稳妥的方式,虽然也会提示版本低,但不会终止安装。

    接下来安装macFUSE,这个直接到官网下载最新的安装包就可以了,目前最新的是macFUSE 4.5.0,注意安装完成后请务必重启系统。

    以上准备工作完成后,就可以照着官网Wiki上写的按步骤操作了。其间会有下载和编译的过程,终端窗口不会有任何进度提示,还请耐心等候。

    首先是从Homebrew安装NTFS-3G

    brew tap gromgit/homebrew-fuse
    brew install ntfs-3g-mac

    完成之后插上你的NTFS移动硬盘,macOS识别后在终端使用以下命令查找硬盘分区的名称(如/dev/disk1s1)

    diskutil list

    确定之后就可以手动挂载NFTFS分区了,注意把下面命令中的/dev/disk1s1更换成你硬盘实际分区的名称

    # 还没弹出移动硬盘的话要先把分区卸载掉
    sudo diskutil unmount /dev/disk1s1
    # 再用 ntfs-3g 挂载硬盘
    sudo mkdir /Volumes/NTFS
    sudo /usr/local/bin/ntfs-3g /dev/disk1s1 /Volumes/NTFS -o local -o allow_other -o auto_xattr -o auto_cache
    # 或者上条命令也可以换成下面这条
    sudo /usr/local/sbin/mount_ntfs /dev/disk1s1 /Volumes/NTFS

    代码输入之后,桌面上应该就会显示出来新挂载的NTFS硬盘了,名字会变成NTFS-3G相关的名字,但内容是没错的。打开之后就可以写入数据了。不用的时候点右键推出就好,再用的时候再用命令来挂载。

    至于自动挂载,官方Wiki也给了方法。但由于大多数情况用的还是移动硬盘来回拷贝文件,而不是台式机内多挂了一个硬盘,所以在这里就不再详述了,有需求的直接去Wiki查阅就好。