九仞之行 https://styunlen.cn 严于律己,宽以待人,深自警省,讷言敏行 Thu, 05 Feb 2026 06:10:12 +0000 zh-Hans hourly 1 https://wordpress.org/?v=6.9.4 如何让neovim 集成 jupyter https://styunlen.cn/archives/post-1711.html https://styunlen.cn/archives/post-1711.html#comments Thu, 05 Feb 2026 04:28:33 +0000 https://styunlen.cn/?p=1711 前言

虽然可以为jupyter安装 jupyter-lsp 插件,实现 pythonlsp 相关功能,但是编码体验仍然不如我平时使用的 neovim,在查阅github后,发现已有neovim插件实现了这方面的功能,不过配置起来略微有些繁琐,顾决定将操作步骤记录下来,以便于后者学习参考。

前置环境

我的系统和相关工具链版本

  • OS: macOS 15.3 arm64
  • Kernel: 24.3.0
  • Packages: 171 (brew)
  • Shell: zsh 5.9
  • NVIM v0.10.4
  • Pyenv 2.4.23 + Python 3.12.8

博主 neovim 配置模板用的基于 lazy.nvimLazyVim ,所以一会儿也会将 molten-nvim插件中的教程转换为LazyVim插件的形式,其他配置文件模板请参考对应模板的语法。不过现阶段几乎所有的 neovim 模板都基于 lazy.vim,所以插件其实是通用的。

以下是一会儿需要用到的neovim 插件。

安装 molten 所需 python 依赖

此处参考文档1 和文档 2

其中,molten 会用到 python,博主使用 zinit 来管理 zsh 的相关插件,pyenv 是通过 zinit 来安装的,以下是我的配置参考

## pyenv
export PYENV_ROOT=$ZPFX/pyenv;
export WORKON_HOME=$PYENV_ROOT/versions
export PROJECT_HOME=$PYENV_ROOT/Devel
zinit ice wait lucid as"program" depth"1" cloneopts"" pick"$ZPFX/pyenv/libexec/pyenv" id-as'pyenv' run-atpull \
  atclone"echo pyenv installing at clone; export PYENV_ROOT=$ZPFX/pyenv; echo PYENV_ROOT: $ZPFX/pyenv; curl -fsSL https://pyenv.run | bash; $ZPFX/pyenv/libexec/pyenv init - | tee $ZPFX/pyenv/init.zsh" \
  atpull"echo pyenv updating at pull; %atclone" \
  atinit"source $ZPFX/pyenv/init.zsh;"
zinit light zdharma-continuum/null

molten的文档中可以注意到,他用的是 virtualenvs来管理插件所需要的 python 依赖包,这里我用的是 pyenv,所以使用的是 pyenv 自带的 pyenv-virtualenv插件来创建虚拟环境,原理大同小异,也可以自定义使用其他虚拟包环境管理器。

使用以下命令创建并激活一个名为 neovim 的虚拟环境,随后使用 pip 安装 molten 所需 python 依赖。

pyenv virtualenv neovim
pyenv activate neovim
pip install pynvim jupyter_client cairosvg plotly kaleido pnglatex pyperclip nbformat

接着按照文档,修改 neovim 的配置,使其能找到当前虚拟环境中的 python

这里要注意,请先使用以下命令查看自己的 neovim 虚拟环境安装目录中,python 的路径

echo $VIRTUAL_ENV/bin/python3

博主的输出是如下内容

/Users/styunlen/.zinit/polaris/pyenv/versions/neovim/bin/python3

所以接下来我就在 neovim 的配置里写以下内容,如果你也像我一样在 zshrc 中声明了WORKON_HOME变量为$PYENV_ROOT/versions,则可以使用下面注释掉的那个写法

vim.g.python3_host_prog = vim.fn.expand("~/.zinit/polaris/pyenv/versions/neovim/bin/python3")
-- vim.g.python3_host_prog = os.getenv("WORKON_HOME") .. "/neovim/bin/python3"

这些都操作完之后,我们进入工作目录,比如我的工作目录为~/WorkDir/Comp,然后重新使用 pyenv(可以自定义)创建并激活一个包含数据分析、人工智能、数据可视化等常见python模块的虚拟环境common

然后安装 ipykernel包,并添加一个名为 neovimipykernel核心,用于 jupyter 后期使用。

以下是相关命令:

pyenv virtualenv common
pyenv activate common
pip install ipykernel
python -m ipykernel install --user --name neovim

配置 neovim

此处参考文档 3

主要用于添加 neovimipynb 的直接编辑支持

由于我使用 LazyVim,所以单独编写一个 lua 文件来配置与 molten 相关插件。如不想看详细步骤,可以直接跳转到第二页复制完整配置

使用以下命令配置插件

nvim ~/.config/nvim/lua/plugins/molten.lua

然后将最开始提到过的插件都加入其中,搭建一个框架

return {
  {
    "benlubas/molten-nvim",
    version = "^1.0.0", -- use version <2.0.0 to avoid breaking changes
    dependencies = { "3rd/image.nvim" },
    build = ":UpdateRemotePlugins",
    init = function()
    end,
  },
  { -- requires plugins in lua/plugins/treesitter.lua and lua/plugins/lsp.lua
    -- for complete functionality (language features)
    "quarto-dev/quarto-nvim",
    ft = { "quarto", "markdown" },
    dev = false,
    config = function()
    end,
    dependencies = {
      -- for language features in code cells
      -- configured in lua/plugins/lsp.lua and
      -- added as a nvim-cmp source in lua/plugins/completion.lua
      "jmbuhr/otter.nvim",
      "nvim-treesitter/nvim-treesitter",
    },
  },
  {
    "GCBallesteros/jupytext.nvim",
    opts = {
      style = "markdown",
      output_extension = "md",
      force_ft = "markdown",
    },
  },
  { -- preview equations
    "jbyuki/nabla.nvim",
    keys = {
      { "<leader>qm", ':lua require"nabla".toggle_virt()<cr>', desc = "toggle [m]ath equations" },
    },
  },
  {
    "nvim-lualine/lualine.nvim",
    dependencies = {
      "benlubas/molten-nvim",
    },
    event = "VeryLazy",
    opts = function(_, opts)
      table.insert(opts.sections.lualine_y, {
        function()
          return require("molten.status").kernels() .. " (ipykernel)"
        end,
      })
    end,
  },
  {
    -- see the image.nvim readme for more information about configuring this plugin
    "3rd/image.nvim",
    opts = {
      backend = "kitty", -- whatever backend you would like to use
      processor = "magick_cli", -- or "magick_cli"
      max_width = 100,
      max_height = 12,
      max_height_window_percentage = math.huge,
      max_width_window_percentage = math.huge,
      window_overlap_clear_enabled = true, -- toggles images when windows are overlapped
      window_overlap_clear_ft_ignore = { "cmp_menu", "cmp_docs", "" },
    },
  },
}

接下来的步骤主要是分布完成 neovimjupyter 以下四个功能的集成

  • 代码运行
  • 结果预览
  • markdownPython LSP 的相关功能 (自动补全, 定义引用跳转, 快速重命名, 代码格式化等)
  • 文件格式转换

上述框架中已经完成了插件集成和联动,比如 lualine 显示当前激活的 ipykernel,但还没有添加快捷键映射、文件自动处理等功能,我们接下来根据上述框架,从上到下一一完善这个配置。

benlubas/molten-nvim

首先完善benlubas/molten-nvim插件的 init 方法,主要实现了以下几个功能

  • neovim 能找到我们为 molten 插件单独创建的虚拟环境,上面有个地方就是添加此处配置,只不过我把他集成到了此处
  • 开启一些官方推荐的插件选项
  • 官方推荐的快捷键绑定
  • 打开 ipynb 文件时,自动初始化 moltenquarto插件,并导入已有的运行结果数据;关闭时,自动导出运行结果数据
  • 添加新建 jupyter notebook的指令

代码如下

init = function()
  local function getenv(var)
    local v = os.getenv(var)
    if v == nil then
      return ""
    else
      return v
    end
  end
  -- vim.g.python3_host_prog=vim.fn.expand("")
  vim.g.python3_host_prog = getenv("WORKON_HOME") .. "/neovim/bin/python3"

  -- I find auto open annoying, keep in mind setting this option will require setting
  -- a keybind for `:noautocmd MoltenEnterOutput` to open the output again
  vim.g.molten_auto_open_output = false

  -- this guide will be using image.nvim
  -- Don't forget to setup and install the plugin if you want to view image outputs
  vim.g.molten_image_provider = "image.nvim"

  -- optional, I like wrapping. works for virt text and the output window
  vim.g.molten_wrap_output = true

  -- Output as virtual text. Allows outputs to always be shown, works with images, but can
  -- be buggy with longer images
  vim.g.molten_virt_text_output = true

  -- this will make it so the output shows up below the \`\`\` cell delimiter
  vim.g.molten_virt_lines_off_by_1 = true
  -- these are examples, not defaults. Please see the readme
  vim.g.molten_image_provider = "image.nvim"
  vim.g.molten_output_win_max_height = 20
  vim.keymap.set("n", "<localleader>mi", ":MoltenInit<CR>", { silent = true, desc = "Initialize the plugin" })
  vim.keymap.set(
    "n",
    "<localleader>e",
    ":MoltenEvaluateOperator<CR>",
    { silent = true, desc = "run operator selection" }
  )
  vim.keymap.set("n", "<localleader>rl", ":MoltenEvaluateLine<CR>", { silent = true, desc = "evaluate line" })
  vim.keymap.set("n", "<localleader>rr", ":MoltenReevaluateCell<CR>", { silent = true, desc = "re-evaluate cell" })
  vim.keymap.set(
    "v",
    "<localleader>rv",
    ":<C-u>MoltenEvaluateVisual<CR>gv",
    { silent = true, desc = "evaluate visual selection" }
  )
  vim.keymap.set("n", "<localleader>rd", ":MoltenDelete<CR>", { silent = true, desc = "molten delete cell" })
  vim.keymap.set("n", "<localleader>oh", ":MoltenHideOutput<CR>", { silent = true, desc = "hide output" })
  vim.keymap.set(
    "n",
    "<localleader>os",
    ":noautocmd MoltenEnterOutput<CR>",
    { silent = true, desc = "show/enter output" }
  )
  -- if you work with html outputs:
  vim.keymap.set(
    "n",
    "<localleader>mx",
    ":MoltenOpenInBrowser<CR>",
    { desc = "open output in browser", silent = true }
  )
  local imb = function(e) -- init molten buffer
    vim.schedule(function()
      vim.cmd("MoltenInit")
      vim.cmd("MoltenImportOutput")
      vim.cmd("QuartoActivate")
    end)
  end
  -- automatically import output chunks from a jupyter notebook
  vim.api.nvim_create_autocmd("BufAdd", {
    pattern = { "*.ipynb" },
    callback = imb,
  })

  -- we have to do this as well so that we catch files opened like nvim ./hi.ipynb
  vim.api.nvim_create_autocmd("BufEnter", {
    pattern = { "*.ipynb" },
    callback = function(e)
      if vim.api.nvim_get_vvar("vim_did_enter") ~= 1 then
        imb(e)
      end
    end,
  })
  -- automatically export output chunks to a jupyter notebook on write
  vim.api.nvim_create_autocmd("BufWritePost", {
    pattern = { "*.ipynb" },
    callback = function()
      if require("molten.status").initialized() == "Molten" then
        vim.cmd("MoltenExportOutput!")
      end
    end,
  })

  -- Provide a command to create a blank new Python notebook
  -- note: the metadata is needed for Jupytext to understand how to parse the notebook.
  -- if you use another language than Python, you should change it in the template.
  local default_notebook = [[
      {
      "cells": [
      {
        "cell_type": "markdown",
        "metadata": {},
        "source": [
          ""
        ]
      }
      ],
      "metadata": {
      "kernelspec": {
        "display_name": "Python 3",
        "language": "python",
        "name": "python3"
      },
      "language_info": {
        "codemirror_mode": {
          "name": "ipython"
        },
        "file_extension": ".py",
        "mimetype": "text/x-python",
        "name": "python",
        "nbconvert_exporter": "python",
        "pygments_lexer": "ipython3"
      }
      },
      "nbformat": 4,
      "nbformat_minor": 5
    }
  ]]

  local function new_notebook(filename)
    local path = filename .. ".ipynb"
    local file = io.open(path, "w")
    if file then
      file:write(default_notebook)
      file:close()
      vim.cmd("edit " .. path)
    else
      print("Error: Could not open new notebook file for writing.")
    end
  end

  vim.api.nvim_create_user_command("NewNotebook", function(opts)
    new_notebook(opts.args)
  end, {
    nargs = 1,
    complete = "file",
  })
end,

quarto-dev/quarto-nvim

文档中在做这一步之前,需要先为 nvim-treesitter 添加一个新的选择器,方便我们在编辑ipynb时,快速上下跳转,可以输入以下命令快捷完成

cat >~/.config/nvim/after/queries/markdown/textobjects.scm <<EOF
;; extends

(fenced_code_block (code_fence_content) @code_cell.inner) @code_cell.outer
EOF

然后配置 quarto-dev/quarto-nvim插件的config函数,主要实现以下几个功能

  • code runner 设置为 molten,连接两个插件
  • 使用一些官方文档推荐的配置和快捷键
  • 扩展nvim-treesitter插件

代码如下

config = function()
  require("quarto").setup({
    lspFeatures = {
      -- NOTE: put whatever languages you want here:
      languages = { "r", "python", "rust" },
      chunks = "all",
      diagnostics = {
        enabled = true,
        triggers = { "BufWritePost" },
      },
      completion = {
        enabled = true,
      },
    },
    keymap = {
      -- NOTE: setup your own keymaps:
      hover = "H",
      definition = "gd",
      rename = "<leader>rn",
      references = "gr",
      format = "<leader>gf",
    },
    codeRunner = {
      enabled = true,
      default_method = "molten",
    },
  })
  local runner = require("quarto.runner")
  vim.keymap.set("n", "<localleader>rc", runner.run_cell, { desc = "run cell", silent = true })
  vim.keymap.set("n", "<localleader>ra", runner.run_above, { desc = "run cell and above", silent = true })
  vim.keymap.set("n", "<localleader>rA", runner.run_all, { desc = "run all cells", silent = true })
  vim.keymap.set("n", "<localleader>rl", runner.run_line, { desc = "run line", silent = true })
  vim.keymap.set("v", "<localleader>r", runner.run_range, { desc = "run visual range", silent = true })
  vim.keymap.set("n", "<localleader>RA", function()
    runner.run_all(true)
  end, { desc = "run all cells of all languages", silent = true })
  require("nvim-treesitter.configs").setup({
    -- ... other ts config
    textobjects = {
      move = {
        enable = true,
        set_jumps = false, -- you can change this if you want.
        goto_next_start = {
          --- ... other keymaps
          ["]b"] = { query = "@code_cell.inner", desc = "next code block" },
        },
        goto_previous_start = {
          --- ... other keymaps
          ["[b"] = { query = "@code_cell.inner", desc = "previous code block" },
        },
      },
      select = {
        enable = true,
        lookahead = true, -- you can change this if you want
        keymaps = {
          --- ... other keymaps
          ["ib"] = { query = "@code_cell.inner", desc = "in block" },
          ["ab"] = { query = "@code_cell.outer", desc = "around block" },
        },
      },
      swap = { -- Swap only works with code blocks that are under the same
        -- markdown header
        enable = true,
        swap_next = {
          --- ... other keymap
          ["<leader>sbl"] = "@code_cell.outer",
        },
        swap_previous = {
          --- ... other keymap
          ["<leader>sbh"] = "@code_cell.outer",
        },
      },
    },
  })
end,

总结

完成以上内容后,就基本完成了所有配置,我们进入工作目录随意打开一个jupyter notebook试试,OK,完成!

参考文档

  1. molten-nvim/docs/Not-So-Quick-Start-Guide.md at main · benlubas/molten-nvim · GitHub
  2. molten-nvim/docs/Virtual-Environments.md at main · benlubas/molten-nvim · GitHub
  3. molten-nvim/docs/Notebook-Setup.md at main · benlubas/molten-nvim · GitHub
]]>
https://styunlen.cn/archives/post-1711.html/feed 1
放弃吧?不!Hue 在 Linux ARM64 上的绝境求生指南 https://styunlen.cn/archives/post-1745.html https://styunlen.cn/archives/post-1745.html#respond Sun, 30 Nov 2025 12:05:45 +0000 https://styunlen.cn/?p=1745 😇 前言

大数据课的坑娃实录:当 Hue 遇上 "薛定谔的环境,本以为 Docker 是避风港,结果它把我推向了更深的技术深渊..."

对象大数据课要求部署 Hue 可视化工具,看着官方 GitHub 上明晃晃的Dockerfile,我拍着胸脯说 "这有何难"—— 毕竟 Docker 的 slogan 不就是 "一次构建,到处运行" 嘛?结果她电脑复杂的环境,差点让我原地轮回。以下是一些问题实录。

🕵️‍♂️ 坑娃破案时刻实录

🔧 Docker 镜像编译历险记:当 Hue 遇上 DNS 迷局

💥 经典剧情:我就知道会出问题

官方说支持 ARM 但不给镜像?没关系,我自己造!—— 直到被 DNS 按在地上摩擦

遇事不决,先 Google 一下,检索问题相关信息,我找到了以下三篇 "秘籍" :

  • ✅ GitHub PR#4149:有人提交了 ARM64 的 CI 流程
  • ✅ Discourse 论坛:官方明确答复 "ARM64 是支持的"(还好没白折腾手动编译)
  • ✅ 官方文档:直接甩出构建命令!抄起键盘就是干

从以上两个链接可以知道,官方其实是支持运行在 arm 上的。不过官方没有提供 arm64docker 镜像,我看有人提交了对应 cicdpr,不过没被 merge

docker build https://github.com/cloudera/hue.git#release-4.11.0 -t gethue/hue:4.11.0 -f tools/docker/hue/Dockerfile
docker tag gethue/hue:4.11.0 gethue/hue:latest

本以为复制粘贴上述代码,编译完镜像就能下班,结果ports.ubuntu.com给我表演了个 "查无此站"——
🌐 科学上网?没用!🔧 换国内源?试过了!📚 翻到 Medium 神文说要改 DNS,测试命令直接给我暴击:

本以为复制粘贴就能下班,结果ports.ubuntu.com给我表演了个"查无此站"——

🌐 科学上网?没用! 🔧 换国内源?试过了!

📚 翻到Medium神文,说要改DNS,测试命令直接给我暴击:

docker run busybox nslookup google.com
;; connection timed out; no servers could be reached

🎯 绝处逢生:给Docker开"后门"

灵机一动加上--network=host参数(感谢Docker网络玄学):

docker run --network=host busybox nslookup google.com

居然活了!活了!赶紧给构建命令也加上这个"续命符",编译进度条终于开始欢快跳动~

docker build --network=host https://github.com/cloudera/hue.git#release-4.11.0 -t gethue/hue:4.11.0 -f tools/docker/hue/Dockerfile
docker tag gethue/hue:4.11.0 gethue/hue:latest

🚀 续命成功但未完待续

搞定镜像编译后启动容器:

docker run \
 -it \
 -p 8888:8888 \
 -v ~/docker/hue/config/hue.ini:/usr/share/hue/desktop/conf/hue.ini \
 --name my-hue-instance \
 --add-host=host.docker.internal:host-gateway \
 --add-host=$(hostname):$(hostname -I | awk '{print $1}') \
 gethue/hue:latest

(内心OS:按照技术折腾守恒定律,这波操作后应该...又要出幺蛾子了吧?)

🌐 网桥崩坏现场:Docker 网络的 "背叛" 与救赎

镜像编译成功≠万事大吉,Docker 用实际行动告诉我:网络问题才是终极 BOSS

🕵️‍♂️ 破案线索:隔离网络的致命破绽

明明--network=host模式下一切正常,换回容器网络立刻"DNS罢工"——这症状像极了Docker网桥在背后捅刀子!

🔍 现场勘查

  • 容器内 ping bilibili.com 不通,curl google.com 也未响应
  • 宿主机网络正常,其他容器也不能上网

结论:docker0网桥接口黑化实锤

⚠️ 高危:网桥重置仪式

(友情提示:以下操作需穿戴"数据防护甲",生产环境请先焚香祷告)

sudo service docker stop # 给Docker断个电
sudo pkill docker # 确保连进程尸体都别剩
# sudo iptables -t nat -F # 【生产环境慎点!】这行是清除所有NAT规则的致命咒语
sudo ifconfig docker0 down # 把故障网桥打入冷宫
sudo brctl delbr docker0 # 物理删除这个叛徒
sudo service docker start # Docker重生仪式

🎉 复活时刻:网络通畅的治愈瞬间

重启后,Docker会自动重建纯洁的docker0网桥,再次运行容器测试网络时——

# 执行
docker run busybox nslookup google.com

DNS解析成功的那一刻,世界都明亮了✨

继续马不停蹄,尝试运行 Hue

🎮 终局之战:Hue 魔丸降服仪式

历经九九八十一难,终于听到了胜利的 BGM!

🔄 重启仪式:给容器最后一次机会

docker restart my-hue-instance

(内心 OS:这次要是再崩,我就把电脑打包送给 Docker 总部当祭品, hhh 开玩笑,我可舍不得😂)

🌐 浏览器朝圣时刻

颤抖着在地址栏输入localhost:8888

⏳ 加载动画像一个世纪那么长

突然!熟悉的 Hue 登录界面带着圣光出现!

(此时应有《权力的游戏》主题曲响起,配合弹幕 "恭迎陛下登基")

😌 总结 & 参考

回顾这趟踩坑之旅:

编译时就被DNS 解析玩起了捉迷藏,运行容器后,网桥接口还是贼心不死。如今看着登录框里闪烁的光标,突然理解了什么叫 "技术人的快乐如此简单"。

以下是我在解决问题时查阅的资料列表,感谢互联网,让我的问题能解决得如此迅速,OK,收工。又水了一篇博文QAQ

]]>
https://styunlen.cn/archives/post-1745.html/feed 0
HomeAssistant 折腾日记——nginx 反向代理配置 https://styunlen.cn/archives/post-1728.html https://styunlen.cn/archives/post-1728.html#respond Tue, 25 Nov 2025 00:19:17 +0000 https://styunlen.cn/?p=1728 前言

最近在折腾改造我们的宿舍,用 docker 搭建了 HomeAssistant,接入米家和一些 esp 设备。我用 frp 技术将 HA 发布到了公网上,以便于我远程控制设备和接受与设备相关的通知。不过为了安全起见,我通过 nginxHomeAssistant 配置了 HTTPS 加密访问。不过,在配置完成之后,出现了一些小问题,这篇博客用来记录一下解决过程

正文

400 Bad Request

server {
    listen       $port ssl;
    server_name  HomeAssistant_https;
    client_max_body_size   1G;

    ssl_certificate      /usr/share/nginx/certs/ssl.crt;
    ssl_certificate_key  /usr/share/nginx/certs/ssl.key;

    ssl_session_cache    shared:SSL:1m;
    ssl_session_timeout  5m;

    ssl_ciphers  HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers  on;

    add_header serverhello $http_host;
    proxy_buffering off;
    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Real-Ip $remote_addr;
        add_header serverhello $http_host;
        proxy_pass http://host.docker.internal:8123;
    }
    error_page 497 https://$http_host$request_uri; #302跳转
}


以上是我的 nginx 反代配置,配置完成了之后发现虽然网页能访问,但是返回 400

原来是 HomeAssistant 为了抵御恶意攻击,要求必须经过信任的 ip 网段发来的代理请求才能被正常响应,因此需要我们修改 HomeAssistant 的配置来允许本地 nginx 的访问。如下图所示

我们在 configuration.yaml 配置文件中添加以下字段,其中 XXX.XXX.XXX.XXX 替换为 nginx 代理的地址

http:
  use_x_forwarded_for: true
  trusted_proxies:
    - XXX.XXX.XXX.XXX # Add the IP address of the proxy server

之后访问就 OK 了

websocket 代理

由于 HomeAssistant 还使用了 websocket,而我上述的 nginx 配置中没有开启 ws 的代理,参考Reverse proxy using NGINX - Community Guides - Home Assistant Community 可知,还需要增加以下字段到 location 配置中:

        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

这篇帖子里面给了我们一段可以参考的 nginx 配置,如下:

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    # Update this line to be your domain
    server_name example.com;

    # These shouldn't need to be changed
    listen [::]:80 default_server ipv6only=off;
    return 301 https://$host$request_uri;
}

server {
    # Update this line to be your domain
    server_name example.com;

    # Ensure these lines point to your SSL certificate and key
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    # Use these lines instead if you created a self-signed certificate
    # ssl_certificate /etc/nginx/ssl/cert.pem;
    # ssl_certificate_key /etc/nginx/ssl/key.pem;

    # Ensure this line points to your dhparams file
    ssl_dhparam /etc/nginx/ssl/dhparams.pem;


    # These shouldn't need to be changed
    listen [::]:443 ssl default_server ipv6only=off; # if your nginx version is >= 1.9.5 you can also add the "http2" flag here
    add_header Strict-Transport-Security "max-age=31536000; includeSubdomains";
    # ssl on; # Uncomment if you are using nginx < 1.15.0
    ssl_protocols TLSv1.2;
    ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;

    proxy_buffering off;

    location / {
        proxy_pass http://127.0.0.1:8123;
        proxy_set_header Host $host;
        proxy_redirect http:// https://;
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }
}

我参考上述配置修改后,总配置如下:

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen       $port ssl;
    server_name  HomeAssistant_https;
    client_max_body_size   1G;

    ssl_certificate      /usr/share/nginx/certs/ssl.crt;
    ssl_certificate_key  /usr/share/nginx/certs/ssl.key;

    ssl_session_cache    shared:SSL:1m;
    ssl_session_timeout  5m;

    ssl_ciphers  HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers  on;

    add_header serverhello $http_host;
    proxy_buffering off;
    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Real-Ip $remote_addr;
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        add_header serverhello $http_host;
        proxy_pass http://host.docker.internal:8123;
    }
    error_page 497 https://$http_host$request_uri; #302跳转
}

结语

折腾了俩小时,成功接入米家,终于能通过 HomeAssistant 操控米家的设备啦!

接下来就是采购各种 esp 板子和传感器,进一步实现智能化寝室啦!

参考

]]>
https://styunlen.cn/archives/post-1728.html/feed 0
《震耳欲聋》观后感(带剧透) https://styunlen.cn/archives/post-1726.html https://styunlen.cn/archives/post-1726.html#respond Fri, 03 Oct 2025 15:37:00 +0000 https://styunlen.cn/?p=1726 昨天看了《震耳欲聋》,片如其名,激起了我对法律本质内涵的思考,以下思考带有部分剧透,未看此影片的同志可以观看后一起思考讨论。

从内涵看,马克思主义法学强调法的本质是阶级统治工具与社会经济结构的统一,法是统治阶级意志的体现,这样的定义毫无疑问是公式化的,没有人情味。而我们生活中的每一个人都是鲜活的存在,各自心中都有自己的道德尺度,都对法律的公平正义有着最朴素的理解与期待,影片的多处情节通过法律与人们心中朴素期待之间的矛盾勾起了我心中难过的情绪,泪水无声宣泄着对片中既得利益者得意的愤怒和对受害者无力发声的同情。

李淇(檀健次饰)与汤宇轩(王戈饰)在最初毫无疑问是市侩律师和坚守法理纯真本质律师的代表。但仔细回溯看李淇的心理发展路线可以看到,他本不如此,作为聋人的健听后代(Coda),求学路上的他敢于为尊严发声,拒绝了消费他 Coda 身份而募捐来的助学金,可在委托张小蕊(兰西雅饰)案件时,却为他当事人张小蕊争取来了靠诈骗聋人群体牟利的邪恶资本启航金融的助学金,这无疑是在追名逐利过程中忘记了自己的初心和作为法律工作者的尊严与良知。

这之间还有个有意思的矛盾对比,李淇作为健听人,所作所为都想努力脱离原生家庭的圈子,活出一个他个人所认为的人样,可在面对现实的不公时,他最初的选择却是与坏人同流合污沆瀣一气,收钱闭嘴不发声,甚至为了自洽自己的行为,美其名曰是为了委托人好。而张小蕊的做法却是积聚力量,“发声”对抗不公。我想他的觉醒不是偶然,小蕊那些无声的比划虽然不动风雨,却比任何有声的慷慨陈词都更有力量,因为每一个手势都是小蕊在努力讨回属于自己的公道,也正是在他作为健听人一次次装聋作哑和小蕊作为听障人一次次努力发声之间的强烈对比,让李淇终于明白了,他来自听障家庭,本该是连接无声世界与有声世界的桥梁,他所谓的 “装聋作哑” 不是妥协,而是对自己身份和公平正义的背叛,于是他找回了自己的良善和初心,选择了为自己的尊严和立场勇敢发声。

电影的结局,李淇虽然成功完成了案件的民转刑,将不法分子送进了大牢,维护住了法律的公平正义,可现实里,理想主义的胜利需要我们在复杂的现实考验中不忘初心,时刻警惕诱惑的腐蚀和理想信念的松懈。以上仅为个人感悟,好电影需自行观看评鉴,希望各位看完之后也能启发各位对法治的思考,也愿每个人未来都能在各行各业为祖国的法治建设贡献自己的力量。

]]>
https://styunlen.cn/archives/post-1726.html/feed 0
让SDKMAN的JDK在macOS上「合法上岗」的全套骚操作 https://styunlen.cn/archives/post-1716.html https://styunlen.cn/archives/post-1716.html#respond Mon, 12 May 2025 05:45:52 +0000 https://styunlen.cn/?p=1716 🚨 事故现场:当SDKMAN遇上java_home

我使用 sdkman 来管理多个 java 版本,每次在终端输入java -version时都岁月静好,直到今天使用 gradle 编译项目时,却突然举着告示牌抗议:「找不到JDK,罢工了!」

重新查看日志,原来是 macos 自带的 /usr/libexec/java_home 程序不识别 sdkman 安装的 java,所以需要我们通过一些小 trick 来骗过 macos,使得 sdkman 安装的 java 也能被系统识别到。

为了增强文章阅读的趣味性,这篇文章我试着使用 ai 来润色和生成文本,今后我的博客也会更多的借助 aigc 的力量,来辅助我讲清楚一些容易被我遗漏的知识细节,让后来的初学者也能更容易理解我的解决思路,并复现我的技术解决方案。

两大阵营的冲突根源

  1. 🍎 苹果原住民JDK 住在/Library/Java/JavaVirtualMachines高档小区,每户都有:
    • 🏠 Contents/Home(真实住宅)
    • 📄 Info.plist(房产证明)
  2. 🚀 SDKMAN太空移民 蜗居在~/.sdkman的集装箱公寓,只有:
    • 🧳 精简版JDK文件
    • ❌ 没有房产证和门牌号

🔧 偷天换日三步走

第1步:伪造身份档案

# 在苹果JDK小区买个虚拟房产
sudo mkdir -p /Library/Java/JavaVirtualMachines/sdkman-current/Contents

第2步:制造时空隧道

# 创建跨次元传送门(<YOUR_USERNAME>替换为你的用户名)
sudo ln -s /Users/<YOUR_USERNAME>/.sdkman/candidates/java/current \
    /Library/Java/JavaVirtualMachines/sdkman-current/Contents/Home

第3步:办理假身份证

<!-- 伪造Info.plist文件 -->

cat <<EOF | sudo tee /Library/Java/JavaVirtualMachines/sdkman-current/Contents/Info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleIdentifier</key>
    <string>sdkman.current</string>
    <key>CFBundleName</key>
    <string>SDKMAN Current JDK</string>
    <key>JavaVM</key>
    <dict>
        <key>JVMPlatformVersion</key>
        <string>9999</string>
        <key>JVMVendor</key>
        <string>Homebrew</string>
        <key>JVMVersion</key>
        <string>9999</string>
    </dict>
</dict>
</plist>
EOF

🧪 验收测试

# 查看当前JDK路径
/usr/libexec/java_home

应该输出:

/Library/Java/JavaVirtualMachines/sdkman-current/Contents/Home

查看所有已识别JDK

/usr/libexec/java_home -V

输出示例:

Matching Java Virtual Machines (1):
    9999 (arm64) "Homebrew" - "SDKMAN Current JDK" /Library/Java/JavaVirtualMachines/sdkman-current/Contents/Home
/Library/Java/JavaVirtualMachines/sdkman-current/Contents/Home

💡 原理黑匣子

系统行为我们的伪装术
扫描标准JDK目录创建虚拟目录结构
检查Info.plist合法性伪造包含必要字段的配置文件
验证JVM实际存在符号链接指向真实SDKMAN路径

版本号9999的阴谋:通过设置超高版本号,确保系统总是优先选择我们的「超级JDK」

⚠️ 注意事项

  1. 如果升级macOS系统,可能需要重新配置

💥 常见翻车现场急救包

症状1:操作后java_home依然装瞎

三连诊断术

ls -l /Library/Java/JavaVirtualMachines/sdkman-current/Contents/Home  # 检查传送门是否畅通
plutil -lint Info.plist  # 验证身份证是否伪造成功
java -version  # 确认SDKMAN当前JDK是否正常

🌟 结语

🚀 神操作总结(一张图看懂)

# 总操作流程图
+------------------+       +------------------+       +------------------+
|  伪造JDK别墅      |       | 创建传送门         |       | 办理9999号身份证  |
|  mkdir -p        |------>| ln -s            |------>| Info.plist      |
+------------------+       +------------------+       +------------------+

🌈 哲学时间

为什么java_home这么固执?
就像机场安检员坚持要检查登机牌,java_home的设计哲学是:

  1. 只相信官方认证的JDK(防止有人携带危险品)
  2. 严格检查目录结构(登机口必须符合IATA标准)
  3. 版本号就是VIP等级(所以我们的9999是超级黑卡)

🎉 最终效果演示

# 在XCode中优雅调用(Build Phase脚本示例)
export JAVA_HOME=$(/usr/libexec/java_home)
./gradlew build --stacktrace # 现在可以愉快甩锅给Android Studio了

📚 引用

]]>
https://styunlen.cn/archives/post-1716.html/feed 0
告别误触复制!tmux 鼠标滚动的正确开启姿势 https://styunlen.cn/archives/post-1714.html https://styunlen.cn/archives/post-1714.html#comments Tue, 18 Mar 2025 11:19:18 +0000 https://styunlen.cn/?p=1714 🐒人类早期驯服tmux实录(错误示范)

最近,因为校园网不稳定,使用 ssh 时会频繁掉线,然后我就需要频繁恢复会话,因此被这个事儿弄得烦躁后,老老实实学习了 tmux 的用法,其中,tmux 能通过以下命令或配置开启鼠标模式

tmux set -g mouse on  # 打开潘多拉魔盒

然而当你想要优雅地通过 set -g mouse on 准备享受丝滑滚动时~,下一秒选中文本突然触发「量子纠缠式复制」。。。

明明只是误触,居然把我误触选中的东西给复制下来了。我目前都在用 Mac 写代码, 那平时肯定都用触摸板,单击也是经常有的事儿嘛,怎么我单击一下就判定我是选中状态呢,非常不河狸🦫啊!

🎮 原理级操作指南

在翻阅了一下 tmuxdocs 后,发现他的 mouse-mode 主要有以下功能:

  • 鼠标三件套行为分解:
    • 🖱️ 滚动浏览(想要)
    • 🪟 窗格调整大小(想要)
    • 🪓 选中即复制(想砍)

但同时,他又能通过 unbind 方法,禁用掉部分作用域下的鼠标滚动事件,那思路就很清晰啦

  1. mouse on 先开总闸门
  2. 用 unbind 劫持滚轮事件,悄悄封印 MouseDrag1Pane 事件的复制诅咒

以下是 tmux 配置实现

set -g mouse on
unbind -T root MouseDrag1Pane

当然啦,这样只是关闭了常规模式下的选中复制功能,如果想在 copy 模式,以及开启了 vim 按键映射下的 copy-vi 模式下也关闭选中复制功能,只需要再在配置中加上以下两行就 OK 啦!

unbind -T copy-mode MouseDrag1Pane
unbind -T copy-mode-vi MouseDrag1Pane

至此,终端再也不会在摸鱼时自动复制老板消息啦~

☁️结语 && 引用

⭐️今日成就:获得「鼠标模式调教师」称号(系统认证)

]]>
https://styunlen.cn/archives/post-1714.html/feed 2
inode 到底是什么,Windows 中是否有类似实现? https://styunlen.cn/archives/post-1700.html https://styunlen.cn/archives/post-1700.html#respond Thu, 26 Dec 2024 13:06:31 +0000 https://styunlen.cn/?p=1700 前言

今天操作系统课上老师问了一句 Windows 中是否也有 inode,我想这个概念应该是文件系统里的,无关乎操作系统,但深入研究下去,发现还有更深层次的意义。

正文

inode 是一个 POSIX 协议中定义的数据结构,用来描述文件系统中具体的文件对象(文件、目录等)。

而POSIX 可移植操作系统接口(英语:Portable Operating System Interface,缩写为POSIX)的由来是IEEE 为了在各种UNIX操作系统上运行软件,而定义API等一系列互相关联的标准的总称,其正式称呼为IEEE Std 1003,国际标准名称为ISO/IEC 9945。此标准源于一个大约开始于1985年的项目。

毕竟操作系统那么多,标准协会还是希望大家在保留个性的同时,拥有行业共性,以便于开发者和用户方便开发与使用软件,现在大家即使使用的是不同的平台(Windows、MacOS),也拥有差不多的软件生态,标准的制定与行业执行功不可没。

以下是维基百科对 inode 的定义

The inode (index node) is a data structure in a Unix-style file system that describes a file-system object such as a file or a directory. Each inode stores the attributes and disk block locations of the object's data.[1] File-system object attributes may include metadata (times of last change,[2] access, modification), as well as owner and permission data.[3]

A directory is a list of inodes with their assigned names. The list includes an entry for itself, its parent, and each of its children.

Linux 和 MacOS 都是典型的类Unix操作系统,拓展一点,其实 Linux 只是个内核,但因为 Linux Kernel 开源,社区和企业会根据自己的需求对上游的 Linux Kernel 做出个性化修改,所以更多时候 Linux 表示的是一系列发行版( Linux内核 + 软件生态 )的总称

你能看到的像是Archlinux、Fedora、Redhat、Ubuntu、Debian、SUSE、Gentoo、NixOS……都叫 Linux,准确说就是 Linux 的发行版。还有个有意思的丶就是,如果你拆开看 Linux ,其实他是 Linux is not unix 的递归简写。

其次POSIX 是 Unix 的标准,1974年,贝尔实验室正式对外发布Unix。因为涉及到反垄断等各种原因,加上早期的Unix不够完善,于是贝尔实验室以慷慨的条件向学校提供源代码,所以Unix在高校中获得了很多支持与发展。于是出现了好多独立开发,且与Unix基本兼容但又不完全兼容的OS,统称为Unix-like OS。Linux 和 MacOS 就是两个类 Unix 系统,自然大部分支持 POSIX 标准,所以在 MacOS 和 Linux 下运行 ls -i 都可以看到 inode 编号,但是 Windows 只支持了部分 POSIX 标准,命令行中 ls 甚至都无法运行。但他也有类似于 inode 一样用于标记和描述文件的数据结构,文档链接:FILE_ID_INFO (winbase.h) - Win32 apps | Microsoft Learn

typedef struct _FILE_ID_INFO {
  ULONGLONG   VolumeSerialNumber;
  FILE_ID_128 FileId;
} FILE_ID_INFO, *PFILE_ID_INFO;

可以看到这个数据结构与 inode 相似。

虽然 Windows 对 POSIX 标准支持不完全,但 POSIX 对我们如今计算机的影响是方方面面的,例如一些基础的系统工具,像是C编译器,以及编程的接口、shell 程序(Windows 没有, Windows 里类似的软件是 cmd 控制台)等等,都因为 POSIX 标准得到了统一。

想必大家有时也会苦恼为什么这个软件在Windows 上有,在安卓上没有,虽然一个是桌面操作系统一个是移动操作系统,但是刚才提到的 C 编译器等基础软件一定是无视操作系统差异甚至无视 CPU 架构差异同时兼容两个系统的。也正是这些基础工具的存在,才使得我们要移植一些程序到其他平台才变得相对容易很多,如果对桌面应用开发很熟悉的话,一定听说过 electron 这个跨平台框架,他利用的就是浏览器的跨平台特性来实现最小的成本开发跨平台应用程序,现在使用 QQ NT 架构的最新版 QQ 就是基于此框架开发的。而浏览器为啥能跨平台呢?前面已经解释过了,基础工具都是通用的,那么想做到跨平台有何不可呢?也许未来的某一天,我们能迎来一个真正的通用操作系统,无视移动端与桌面端的交互差异,打通移动与桌面的生态,兼具移动端的轻便与桌面端的效率。那时候再说什么跨平台都显得有些过时了吧。

对于 POSIX 的一些统一,可以给大家举个例子,以下是我的两台设备,一个是在 ARM 芯片上运行的 MacOS,一个是在 X86 芯片上运行的 Archlinux,学过计组的我们知道,不同芯片架构的指令集是不一样的,ARM 是精简指令集,X86 是复杂指令集,大家可以看到,两个拥有着截然不同 CPU 架构与操作系统的环境,却可以同时运行同一个程序 neofetch、拥有同一个 shell 环境和 gcc 编译器,虽然这也是开源运动(可以单开一篇讲)的功劳,但是在以后将要学习的计算机网络等专业课里,我们还能接触到很多类似的标准与协议,也许我们不知道他为何设计,但我们确实都已经受益于中了。

引用与扩展阅读

]]>
https://styunlen.cn/archives/post-1700.html/feed 0
使用udisk来让Linux自动挂载USB硬盘和U盘 https://styunlen.cn/archives/post-1696.html https://styunlen.cn/archives/post-1696.html#comments Tue, 29 Oct 2024 15:25:09 +0000 https://styunlen.cn/?p=1696 前言

虽然可以使用fstab 来在开机启动时自动挂载和计算机连接的硬盘设备,但是在系统运行阶段,对于新接入的设备,往往没有他的设备信息,此时如何让linux windows 一样自动挂载接入设备的分区呢。

这里我使用的工具是systemdudisks,以下是我的linux发行版简要信息:

OS: Arch Linux x86_64 
Kernel: 6.11.5-zen1-1-zen 
Shell: zsh 5.9 
DE: Plasma 6.2.2 
Memory: 8807MiB / 31570MiB 

除开各个发行版安装软件包的差异,以下内容为通用解决方案

正文

udisk是作为一个服务进程运行的,并提供了 udisksctl(1) 命令行工具来与其交互,从而实现对系统存储设备的管理。

安装udisk2

首先安装udisk2

pacman -S udisk2

添加polkit权限

安装完毕后,在使用这个工具之前,我们要先为他添加polkit权限,使得用户执行此命令时可以免密执行。

sudo touch /etc/polkit-1/rules.d/50-udiskie.rules
sudo chmod 644 /etc/polkit-1/rules.d/50-udiskie.rules
sudo gpasswd -a <username> storage # <username>替换成自己的用户名   

并为/etc/polkit-1/rules.d/50-udiskie.rules添加以下内容

polkit.addRule(function(action, subject) {
  var YES = polkit.Result.YES;
  var permission = {
    // required for udisks1:
    "org.freedesktop.udisks.filesystem-mount": YES,
    "org.freedesktop.udisks.luks-unlock": YES,
    "org.freedesktop.udisks.drive-eject": YES,
    "org.freedesktop.udisks.drive-detach": YES,
    // required for udisks2:
    "org.freedesktop.udisks2.filesystem-mount": YES,
    "org.freedesktop.udisks2.encrypted-unlock": YES,
    "org.freedesktop.udisks2.eject-media": YES,
    "org.freedesktop.udisks2.power-off-drive": YES,
    // required for udisks2 if using udiskie from another seat (e.g. systemd):
    "org.freedesktop.udisks2.filesystem-mount-other-seat": YES,
    "org.freedesktop.udisks2.filesystem-unmount-others": YES,
    "org.freedesktop.udisks2.encrypted-unlock-other-seat": YES,
    "org.freedesktop.udisks2.encrypted-unlock-system": YES,
    "org.freedesktop.udisks2.eject-media-other-seat": YES,
    "org.freedesktop.udisks2.power-off-drive-other-seat": YES
  };
  if (subject.isInGroup("storage")) {
    return permission[action.id];
  }
});

此时只要是在storage用户组内的用户在执行udisksctl时,都将可以使用--no-user-interaction参数来跳过输入密码的过程。

添加监听服务进程

udevadm monitor

You may use udevadm monitor to monitor block events and mount drives when a new block device is created. Stale mount points are automatically removed by udisksd, such that no special action is required on deletion.

—— udisks - ArchWiki

udevadm monitor指令实现了对block events的监听,我们可以利用这个指令来获取新接入设备的blkid等信息,然后调用udisksctl挂载分区,从而实现新接入设备的自动挂载。好在archlinux wiki已经提供了一个示范脚本来实现此功能

#!/bin/sh

pathtoname() {
    udevadm info -p /sys/"$1" | awk -v FS== '/DEVNAME/ {print $2}'
}

stdbuf -oL -- udevadm monitor --udev -s block | while read -r -- _ _ event devpath _; do
        if [ "$event" = add ]; then
            devname=$(pathtoname "$devpath")
            udisksctl mount --block-device "$devname" --no-user-interaction
        fi
done

将他保存到~/.local/bin/start-udevadm-monitor中(其他路径也可以,别忘了赋予可执行权限),我的家目录为/home/styunlen,请根据自己的用户名做更改

然后再创建一个systemd服务来在开机启动时自动运行这个脚本,这样我们就能持续监听硬盘接入事件,并自动挂载他啦~

首先使用下列命令创建service文件,并将他的文件权限改为644

sudo touch /etc/systemd/system/udevadm-monitor.service
sudo chmod 644 /etc/systemd/system/udevadm-monitor.service

然后输入以下内容

[Unit]
Description=udevadm-monitor

[Service]
WorkingDirectory=/home/styunlen/.local/bin
ExecStart=/home/styunlen/.local/bin/start-udevadm-monitor
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

最后输入以下命令启用他,就可以实现自动监听硬盘接入事件,并且自动挂载啦~

sudo systemctl enable --now udevadm-monitor.service

引用

]]>
https://styunlen.cn/archives/post-1696.html/feed 4
在Arch Liunx里只用Intel Ultra 7 155H跑stable-diffusion-webui https://styunlen.cn/archives/post-1679.html https://styunlen.cn/archives/post-1679.html#respond Fri, 28 Jun 2024 07:32:35 +0000 https://styunlen.cn/?p=1679 引言

虽然我把arch linux装在了移动硬盘里,使得我可以同时在我的轻薄本和游戏本之间运行同一个系统,并拥有一个系统级同步的开发环境,但由于我之前将stable-diffusion-webui部署在了游戏本的win11上,这就导致我无法在带着轻薄本出门时使用stable-diffusion-webui来生成一些简单的demo。我的生图需求并不高,不需要生成复杂的模型和图片,所以没必要等着回去用游戏本或者用在线模型来满足这个需求。其次,我的游戏本和轻薄本都是win11与移动硬盘上的linux组成的双系统,这次我选择将其迁移到我的arch-linux中,方便在两台电脑都跑通stable-diffusion-webui

折腾记录

第一个要解决的问题肯定是python版本问题,因为arch默认使用最新的python,

stable-diffusion-webui文档(截至博客发布前)里推荐使用 python 3.10.6, 所以我选择pyenv来管理多版本python, 使用以下命令安装pyenvpython 3.10.6

sudo pacman -S pyenv
pyenv install 3.10.6

最新版的python我尝试了会报依赖错误,

我是报了类似这个帖子的错误ERROR: Failed building wheel for tokenizers

手动装torch也报错

pip install torch==2.1.2 torchvision==0.16.2 --extra-index-url https://download.pytorch.org/whl/cu121

ERROR: Could not find a version that satisfies the requirement torch==2.1.2 (from versions: 2.2.0, 2.2.0+cu121, 2.2.1, 2.2.1+cu121, 2.2.2, 2.2.2+cu121, 2.3.0, 2.3.0+cu121, 2.3.1, 2.3.1+cu121)

Github上截至博客发布前也有相关issues

Webui not working with Python 3.12 [Bug]: · Issue #15667 · AUTOMATIC1111/stable-diffusion-webui · GitHub

系统级python降级是不可能的,我也懒得折腾,所以还是用pyenv搞一个文档推荐的python环境来解决这个问题

克隆仓库,并修改python为指定版本

git clone https://github.com/openvinotoolkit/stable-diffusion-webui.git
cd stable-diffusion-webui
alias python=/home/styunlen/.pyenv/versions/3.10.6/bin/python
alias python3=/home/styunlen/.pyenv/versions/3.10.6/bin/python 
python -m venv venv
source venv/bin/activate

webui.sh中的python路径也可以手动改为以下内容

# python3 executable
# if [[ -z "${python_cmd}" ]]
# then
#     python_cmd="python3"
# fi
python_cmd="/home/$USER/.pyenv/versions/3.10.6/bin/python"

接下来requirements.txt中的依赖可以使用pip安装也可以直接运行./webui.sh来自动安装

export PYTORCH_TRACING_MODE=TORCHFX
export COMMANDLINE_ARGS="--skip-torch-cuda-test --precision full --no-half" 
./webui.sh 
# 或 pip install -r requirements.txt 后运行webui.sh

模型下载太慢的话可以用下载器下载,然后复制到当前目录就行了

512X512一分钟,虽然很慢,将就用用吧。

保存一下启动脚本,方便启动

cat << EOF > start.sh
export PYTORCH_TRACING_MODE=TORCHFX
export COMMANDLINE_ARGS="--skip-torch-cuda-test --precision full --no-half" 
./webui.sh 
EOF

参考文档

Installation on Intel Silicon · openvinotoolkit/stable-diffusion-webui Wiki · GitHub

Install and Run on AMD GPUs · AUTOMATIC1111/stable-diffusion-webui Wiki · GitHub

]]>
https://styunlen.cn/archives/post-1679.html/feed 0
浙江财经大学校庆50周年之“三行代码作情诗” https://styunlen.cn/archives/post-1670.html https://styunlen.cn/archives/post-1670.html#comments Thu, 11 Apr 2024 07:25:36 +0000 https://styunlen.cn/?p=1670 代码

抽象版本如下

while (styunlen.life.duration > 0) {
  styunlen.love.eternal("ZUFE");
}

我为其添加了可运行的版本

/**
 * @author Styunlen
 * @dedicated to ZUFE for her 50th anniversary
 * @edit time 2024.04.11 15:15:20 
 **/
// 抽象版本
// while (styunlen.life.duration > 0) {
//   styunlen.love.eternal("ZUFE");
// }

// 可运行版本
let y = 30,x = -60,scale = [.1, .09, .08, .07, .06, .05, .06, .07, .08, .09, .1],index = 0,range = (size) => [...Array(size)];
let getScale = () => scale[index % scale.length],fn = (x) => ((getScale()**2*(4*y*y+x*x)-1)**3-8*x*x*y**3*getScale()**5 <= 0);
const clear = () => process.stdout.write(process.platform === "win32" ? "\x1bc" : "\x1b[2j\x1b[3j\x1b[h",);
let styunlen = {life: {duration: "浙江财经大学校庆50周年"}},loveindex = 0,scheduler = (await import("timers/promises")).scheduler;
styunlen.love = {eternal: async (sweetheart) => {console.log(((y = 30),index++,range(60).map(() => (y--,(x = -60),range(120).map(() => (fn(x++) ? sweetheart.at(loveindex++ % sweetheart.length) : " ")).join("")),).join("\n\x1b[91m")),);await scheduler.wait(50);clear();},};
while (styunlen.life.duration.length > 0) {
  await styunlen.love.eternal("ZUFE ");
}

运行说明,由于使用了JSES Module写法, 所以要手动将js文件后缀命名成.mjs来强制nodejsES Module形式加载js,否则会报错。

另一种让nodejs.js文件视为ES Module的方法是使用package.jsontype属性,手动将json文件里的type属性设置为"module"就行。

核心代码就是使用jsArray.prototype.mapArray.prototype.join来最小化代码生成爱心字符串,以及数学公式( x²+y²-1)³-x²y³ <= 0来判断是否在爱心区域内。

上述代码中的getScale也如字面意思是为了调整x,y的缩放系数,以适应控制台的长宽比(粗暴地使用了x缩放两倍,y缩放一倍,因为系数是小数,所以看起来y乘了2,其实是缩小了一倍 )。

(getScale()**2*(4*y*y+x*x)-1)**3-8*x*x*y**3*getScale()**5 <= 0

以上代码也只是以下代码的因式分解压缩形式,看不懂的可以自己写草稿运算一遍。

((x * getScale()) ** 2 + (y * getScale() * 2) ** 2 - 1) ** 3 -(x * getScale()) ** 2 * (y * getScale() * 2) ** 3

运行效果

windows: 

linux:

]]>
https://styunlen.cn/archives/post-1670.html/feed 2