Obsidian通过Dropbox同步实现文章自动发布

graph LR 网易云笔记-->Evernote; Evernote-->为知笔记; 为知笔记-->Evernote; Evernote-->Joplin; Joplin-->Obsidian; 现在用的Obsidian是Markdown支持最好的,可以说写文档是一种享受了,但是要用官方的同步还是付费,于是就找有没有同步的方案,没有采用第三方插件Remotely Save或者是Remotely Sync,而是采用Dropbox直接同步文件形式,个人使用,没有太多的协同编写的场景。 graph TD B("fab:fa-dropbox Dropbox") B<-->C["fa:fa-laptop Obsidian maestral"] B<-->D["fa:fa-mobile-screen-button Obsidian Dropsync"] B<-->E["fab:fa-linux Hugo maestral"] PC端用maestral先同步dropbox文件,然后Obsidian直接在dropbox的同步目录创建仓库,之后编辑的内容就会实时同步了,可以Show Recent Change 功能查看已经同步的变更文件,有冲突的文件会有提示。 移动端使用Dropsync同步文件,然后Obsidian打开仓库,在PC安装的插件和设置都可以在移动端使用。需要注意的是,因为Dropsync 不是采用文件变化通知机制同步,而是定时同步机制,因此需要在手机端打开Obsidian之前,需要手动同步一下。另外就是最好PC端、移动端不要同时设置或者打开关闭笔记,会有同步文件冲突。有冲突的情况下,可以进入仓库查看笔记或者.obsidian目录下是否存在appearance (conflict 2024-04-15-08-35-19).json 类似的文件。 可以在Dropsync软件的同步历史查看同步日志信息。 服务器端主要用maestral在命令行下运行同步Dropbox文件,然后通过脚本把对应笔记md文件拷贝到 Hugo 目录下,编译发布成个人站点文章。服务器没有设置成一有文件变更就同步更新到Hugo,感觉有些频繁,我是设置了脚本的定时任务,每天晚上编译发布,确实想实时发布的,就到服务器上手动执行一下脚本。 整个流程下来对于使用基本是无感的,同步速度也很快,基本在PC端编辑完之后,你登录上服务器就可以看到同步好的文件。 用到的软件: Obsidian: https://obsidian.md/ Maestral: https://maestral.app/ Dropsync: https://metactrl.com/#our-apps

Hugo代码块固定高度设置

Hugo个人博客搭建过程参考Hugo个人博客搭建 新增code的css mkdir -p assets/css/extended vim assets/css/extended/code.css code.css内容如下: .post-content pre code { max-height: 40em; /* 根据需要调整高度 */ overflow: auto; } 设置目录到左侧是参考3rd’s Blog的博客 感谢

Hugo文章TOC固定在左侧显示

Hugo个人博客搭建过程参考Hugo个人博客搭建 在layouts/partials目录下创建toc.html vim layouts/partials/toc.html toc.html内容为: {{- $headers := findRE "<h[1-6].*?>(.|\n])+?</h[1-6]>" .Content -}} {{- $has_headers := ge (len $headers) 1 -}} {{- if $has_headers -}} <aside id="toc-container" class="toc-container wide"> <div class="toc"> <details {{if (.Param "TocOpen") }} open{{ end }}> <summary accesskey="c" title="(Alt + C)"> <span class="details">{{- i18n "toc" | default "Table of Contents" }}</span> </summary> <div class="inner"> {{- $largest := 6 -}} {{- range $headers -}} {{- $headerLevel := index (findRE "[1-6]" . 1) 0 -}} {{- $headerLevel := len (seq $headerLevel) -}} {{- if lt $headerLevel $largest -}} {{- $largest = $headerLevel -}} {{- end -}} {{- end -}} {{- $firstHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers 0) 1) 0)) -}} {{- $.Scratch.Set "bareul" slice -}} <ul> {{- range seq (sub $firstHeaderLevel $largest) -}} <ul> {{- $.Scratch.Add "bareul" (sub (add $largest .) 1) -}} {{- end -}} {{- range $i, $header := $headers -}} {{- $headerLevel := index (findRE "[1-6]" . 1) 0 -}} {{- $headerLevel := len (seq $headerLevel) -}} {{/* get id="xyz" */}} {{- $id := index (findRE "(id=\"(.*?)\")" $header 9) 0 }} {{- /* strip id="" to leave xyz, no way to get regex capturing groups in hugo */ -}} {{- $cleanedID := replace (replace $id "id=\"" "") "\"" "" }} {{- $header := replaceRE "<h[1-6].*?>((.|\n])+?)</h[1-6]>" "$1" $header -}} {{- if ne $i 0 -}} {{- $prevHeaderLevel := index (findRE "[1-6]" (index $headers (sub $i 1)) 1) 0 -}} {{- $prevHeaderLevel := len (seq $prevHeaderLevel) -}} {{- if gt $headerLevel $prevHeaderLevel -}} {{- range seq $prevHeaderLevel (sub $headerLevel 1) -}} <ul> {{/* the first should not be recorded */}} {{- if ne $prevHeaderLevel . -}} {{- $.Scratch.Add "bareul" . -}} {{- end -}} {{- end -}} {{- else -}} </li> {{- if lt $headerLevel $prevHeaderLevel -}} {{- range seq (sub $prevHeaderLevel 1) -1 $headerLevel -}} {{- if in ($.Scratch.Get "bareul") . -}} </ul> {{/* manually do pop item */}} {{- $tmp := $.Scratch.Get "bareul" -}} {{- $.Scratch.Delete "bareul" -}} {{- $.Scratch.Set "bareul" slice}} {{- range seq (sub (len $tmp) 1) -}} {{- $.Scratch.Add "bareul" (index $tmp (sub . 1)) -}} {{- end -}} {{- else -}} </ul> </li> {{- end -}} {{- end -}} {{- end -}} {{- end }} <li> <a href="#{{- $cleanedID -}}" aria-label="{{- $header | plainify -}}">{{- $header | safeHTML -}}</a> {{- else }} <li> <a href="#{{- $cleanedID -}}" aria-label="{{- $header | plainify -}}">{{- $header | safeHTML -}}</a> {{- end -}} {{- end -}} <!-- {{- $firstHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers 0) 1) 0)) -}} --> {{- $firstHeaderLevel := $largest }} {{- $lastHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers (sub (len $headers) 1)) 1) 0)) }} </li> {{- range seq (sub $lastHeaderLevel $firstHeaderLevel) -}} {{- if in ($.Scratch.Get "bareul") (add . $firstHeaderLevel) }} </ul> {{- else }} </ul> </li> {{- end -}} {{- end }} </ul> </div> </details> </div> </aside> <script> let activeElement; let elements; window.addEventListener('DOMContentLoaded', function (event) { checkTocPosition(); elements = document.querySelectorAll('h1[id],h2[id],h3[id],h4[id],h5[id],h6[id]'); // Make the first header active activeElement = elements[0]; const id = encodeURI(activeElement.getAttribute('id')).toLowerCase(); document.querySelector(`.inner ul li a[href="#${id}"]`).classList.add('active'); }, false); window.addEventListener('resize', function(event) { checkTocPosition(); }, false); window.addEventListener('scroll', () => { // Check if there is an object in the top half of the screen or keep the last item active activeElement = Array.from(elements).find((element) => { if ((getOffsetTop(element) - window.pageYOffset) > 0 && (getOffsetTop(element) - window.pageYOffset) < window.innerHeight/2) { return element; } }) || activeElement elements.forEach(element => { const id = encodeURI(element.getAttribute('id')).toLowerCase(); if (element === activeElement){ document.querySelector(`.inner ul li a[href="#${id}"]`).classList.add('active'); } else { document.querySelector(`.inner ul li a[href="#${id}"]`).classList.remove('active'); } }) }, false); const main = parseInt(getComputedStyle(document.body).getPropertyValue('--article-width'), 10); const toc = parseInt(getComputedStyle(document.body).getPropertyValue('--toc-width'), 10); const gap = parseInt(getComputedStyle(document.body).getPropertyValue('--gap'), 10); function checkTocPosition() { const width = document.body.scrollWidth; if (width - main - (toc * 2) - (gap * 4) > 0) { document.getElementById("toc-container").classList.add("wide"); } else { document.getElementById("toc-container").classList.remove("wide"); } } function getOffsetTop(element) { if (!element.getClientRects().length) { return 0; } let rect = element.getBoundingClientRect(); let win = element.ownerDocument.defaultView; return rect.top + win.pageYOffset; } </script> {{- end }} 修改toc的css ...

四月 14, 2024 · 4 分钟 · 838 字 · Byter ·  Hugo ·  TOC

Hugo日子列表和文章头部显示标签

Hugo个人博客搭建过程参考Hugo个人博客搭建 在layouts/partials目录下创建 post_meta.html cp themes/hugo-PaperMod-master/layouts/partials/post_meta.html layouts/partials/post_meta.html vim layouts/partials/post_meta.html 将一下部分的内容 {{- if not (.Param "hideAuthor") -}} {{- with (partial "author.html" .) }} {{- $scratch.Add "meta" (slice .) }} {{- end }} {{- end }} 修改为 {{- $author := (partial "author.html" .) }} {{- $tags := (partial "tags.html" .) }} {{- if not (.Param "hideAuthor") -}} {{- if $tags }} {{- $scratch.Add "meta" (slice $author $tags) -}} {{- else}} {{- $scratch.Add "meta" (slice $author) -}} {{- end}} {{- else}} {{- if $tags }} {{- $scratch.Add "meta" (slice $tags) -}} {{- end}} {{- end }} 在layouts/partials目录下创建tags.html ...

四月 14, 2024 · 1 分钟 · 144 字 · Byter ·  Hugo ·  Post ·  Meta

Hugo支持渲染mermaid图

Hugo个人博客搭建过程参考Hugo个人博客搭建 sequenceDiagram participant Alice participant Bob Alice->>Bob: Hello Bob! Bob->>Alice: Hello Alice! 在layouts目录下创建_default目录,拷贝主题下对应的内容 mkdir -p layouts/_default cp themes/hugo-PaperMod-master/layouts/_default/baseof.html layouts/_default/ vim layouts/_default/baseof.html 在文件后面增加以下内容 {{ if .Store.Get "hasMermaid" }} <script type="module"> import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.esm.min.mjs'; mermaid.initialize({ startOnLoad: true }); </script> {{ end }} 在layouts/_default目录下创建_markup目录 mkdir -p layouts/_default/_markup vim layouts/_default/_markup/render-codeblock-mermaid.html render-codeblock-mermaid.html文件内容如下 <pre class="mermaid"> {{- .Inner | safeHTML }} </pre> {{ .Page.Store.Set "hasMermaid" true }}

四月 14, 2024 · 1 分钟 · 61 字 · Byter ·  Hugo ·  Mermaid

Hugo集成Disqus评论

Hugo个人博客搭建过程参考Hugo个人博客搭建 Hugo 包含 Disqus 的嵌入式模板,Disqus 是一种流行的评论系统,适用于静态和动态网站。要有效使用 Disqus,请通过注册免费服务获得 Disqus “shortname”。 Disqus注册连接 signing up 在layouts创建partials再创建comments.html文件vim layouts/partials/comments.html cd bytejog/ mkdir -p layouts/partials vim layouts/partials/comments.html 文件内容为: <div id="disqus_thread"></div> <script type="text/javascript"> (function() { // Don't ever inject Disqus on localhost--it creates unwanted // discussions from 'localhost:1313' on your Disqus account... if (window.location.hostname == "localhost") return; var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; var disqus_shortname = '{{ .Site.Config.Services.Disqus.Shortname }}'; dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); })(); </script> <noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript> <a href="https://disqus.com/" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a> 修改hugo.yaml文件vim hugo.yaml services: disqus: shortname: #shortname

四月 14, 2024 · 1 分钟 · 102 字 · Byter ·  Hugo ·  Disqus

个人音乐服务Navidrome搭建和使用

Navidrome🎧☁️ 与 Subsonic/Airsonic 兼容的现代音乐服务器和串流器。 Navidrome 是一款基于网络的开源音乐收藏服务器和流媒体。它让您可以通过任何浏览器或移动设备自由收听您收藏的音乐。它就像你的个人 Spotify! 特点 处理超大音乐收藏 几乎可串流任何音频格式 读取并使用所有精心策划的元数据 对合集(Various Artists 专辑)和盒装(多碟专辑)的强大支持 多用户,每个用户都有自己的播放次数、播放列表、收藏夹等。 资源使用率极低 多平台,可在 macOS、Linux 和 Windows 上运行。还提供 Docker 映像 所有主要平台(包括 Raspberry Pi)的二进制文件均可随时使用 自动监控资料库变化,导入新文件并重新加载新元数据 基于 Material UI 的可主题化、现代化和响应式网络界面 与所有 Subsonic/Madsonic/Airsonic 客户端兼容 即时转码可按用户/播放器设置。支持 Opus 编码 翻译成各种语言 创建docker文件 采用Docker形式安装服务,准备工作是安装docker服务,参考 Install using the apt repository Navidrome的主目录假定在/opt/navidrome , 创建docker compose文件 cd /opt/navidrome vim docker-compose.yml docker-compose.yml 内容 version: "3" services: navidrome: image: deluan/navidrome:develop ports: - "14533:4533" restart: unless-stopped environment: ND_SCANSCHEDULE: 0 ND_LOGLEVEL: info ND_SESSIONTIMEOUT: 24h ND_BASEURL: "/nav" ND_PLAYLISTSPATH: "." ND_LASTFM_LANGUAGE: "zh" ND_LASTFM_APIKEY: "lastfm_apikey" ND_LASTFM_SECRET: "lastfm_secret" ND_SPOTIFY_ID: "spotify_id" ND_SPOTIFY_SECRET: "spotify_secret" ND_ENABLEARTWORKPRECACHE: "false" ND_ENABLESHARING: "true" volumes: - "/opt/navidrome/data:/data" - "/opt/navidrome/music:/music:ro" Navidrome有很多参数Advanced configuration ,使用熟悉了可以自己在环境变量里再增加配置。 ...

Linux修改SSH使用证书登录

由于使用密码方式登录,会有接收到很多尝试爆破的登录连接,考虑禁止SSH用户名密码登录模式,改用证书登录。 生成公钥和私钥 ssh-keygen -t rsa 按照提示生成,提示输入证书密码,可以留空,如果输入了,每次登录还需要输入证书的密码。完成后会在~/.ssh/目录下生成 id_rsa 和 id_rsa.pub 文件。 在服务上将公钥内容添加到 authorized_keys cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys # 不再服务器生成的 vim ~/.ssh/authorized_keys 检查~/.ssh/authorized_keys文件权限 -rw------- 1 xxx xxx 400 Mar 24 20:10 authorized_keys # 如果权限不对,执行以下命令 chmod 600 ~/.ssh/authorized_keys 这个时候可以尝试使用ssh工具用证书登录,本地登录需要将公钥和私钥都保存在一个文件夹,如果登录没有问题,进入下一步 修改sshd配置文件 在/etc/ssh/sshd_config.d下增加一个配置文件,达到覆盖系统默认的/etc/ssh/sshd_config.d 的目的,因为ssh如果升级,会有被覆盖的风险 vim /etc/ssh/sshd_config.d/60-custom.conf 增加以下内容 PasswordAuthentication no 保存后,重启sshd服务 这个时候原来用密码登录的ssh还能用,别着急关(万一有问题可以急救),再试试证书是否能正常登录,用户名密码登录应该已经不能登录。

四月 6, 2024 · 1 分钟 · 46 字 · Byter ·  Linux ·  SSH

2024-03 跑步日记

2024-03 运动次数: 12 运动距离: 110.11 km 运动时长: 11:20:59 平均距离: 9.18 km 平均心率: 147 bpm 平均配速: 6:11 / km 2024-03-03 时间: 2024-03-03 07:59:35 距离: 15.30 km 时长: 1:36:36 配速: 6:18 / km 心率: 148 bpm ...

三月 31, 2024 · 1 分钟 · 210 字 · Jogger ·  跑步

低成本AI音色克隆软件【GPT-SoVITS】使用教程

GPT-SoVITS :https://github.com/RVC-Boss/GPT-SoVITS 官方demo介绍:https://www.bilibili.com/video/BV12g4y1m7Uw 功能: 零样本文本到语音(TTS): 输入 5 秒的声音样本,即刻体验文本到语音转换。 少样本 TTS: 仅需 1 分钟的训练数据即可微调模型,提升声音相似度和真实感。 跨语言支持: 支持与训练数据集不同语言的推理,目前支持英语、日语和中文。 WebUI 工具: 集成工具包括声音伴奏分离、自动训练集分割、中文自动语音识别(ASR)和文本标注,协助初学者创建训练数据集和 GPT/SoVITS 模型。 使用过程简介: 数据处理 UVR5 人声处理 音频切割 音频降噪 音频打标 校对标注 训练 数据集格式化 微调训练 推理 教程 实验环境:Ubuntu 22.04,NVIDIA RTX A6000 采用docker形式部署 docker-compose.yaml文件:https://raw.githubusercontent.com/RVC-Boss/GPT-SoVITS/main/docker-compose.yaml 0. 准备工作 在主目录下创建GPT-SoVITS,并下载docker-compose.yaml,包括模型的镜像大小5G左右 cd ~ mkdir GPT-SoVITS cd GPT-SoVITS mkdir logs output reference SoVITS_weights GPT_weights curl -o docker-compose.yaml https://raw.githubusercontent.com/RVC-Boss/GPT-SoVITS/main/docker-compose.yaml 默认docker-compose.yaml需要做一下调整,需要将 GPT_weights文件夹映射出来。 原始内容: volumes: - ./output:/workspace/output - ./logs:/workspace/logs - ./SoVITS_weights:/workspace/SoVITS_weights - ./reference:/workspace/reference 修改后的内容(增加GPT_weights一行映射): ...

三月 16, 2024 · 2 分钟 · 226 字 · Byter ·  AI ·  TTS