一个基于AI的Typecho辅助生成插件 AIHelper
## 功能
基于AI实现 slug/摘要/标签提取/文章内容生成 等功能
插件设置可以自定义提示词,根据你的需要生成相关内容
下载
[download file='AIhelper.zip' size='16.5kb']https://file.imsun.org/upload/2025-12/AIHelper_NnneT.tar.gz[/download]
本文由 老孙博客 原创发布
转载请注明出处
## 功能
基于AI实现 slug/摘要/标签提取/文章内容生成 等功能
插件设置可以自定义提示词,根据你的需要生成相关内容
[download file='AIhelper.zip' size='16.5kb']https://file.imsun.org/upload/2025-12/AIHelper_NnneT.tar.gz[/download]
本文由 老孙博客 原创发布
转载请注明出处
Typecho 1.2中正常使用的UploadPlugin插件在 1.3 beta中无法正常的上传主题和插件了 非常的不方便
于是修复了上传的问题
下载地址放在下面 自取
[download file='UploadPlugin.zip' size='7.5KB']https://file.imsun.org/upload/2025-11/UploadPlugin (1).zip[/download]
本文由 老孙博客 原创发布
转载请注明出处
因为想用Cloudflare的Workers 和 D1 部署一个完全仿照Wordpress Rest API 的博客后端
所以 用AI糊了一个项目
https://github.com/jkjoy/cfblog
为了能使用这个后端 所以就又糊了一个基于vue3的前端 仿 Akina 风格
https://github.com/jkjoy/cfblog-theme-akina
同时又仿hugo主题paper 糊了一个Astro 的前端
https://github.com/jkjoy/cfblog-theme-paper
为了让这个主题能同时兼容wordpress 和 typecho
于是我又开了一个
https://github.com/jkjoy/Typecho-RestAPI
为了让wordpress 的API路由兼容更好
我又仿sunnylite主题 弄了个wordpress主题
本文由 老孙博客 原创发布
转载请注明出处
一个功能强大的 Typecho RSS 插件,支持全文输出和多种美化显示风格。
/feed 路由下载插件到 Typecho 插件目录:
/usr/plugins/Rss/在 Typecho 后台启用插件:
配置插件(可选):
插件提供 4 种不同风格的主题供选择:
渐变风格 (gradient) - 默认主题
简洁风格 (minimal)
现代风格 (modern)
暗黑风格 (dark)
示例:
<p>本文由 <a href="https://example.com">我的博客</a> 原创发布</p>
<p>转载请注明出处</p>启用插件后,可以通过以下 URL 访问 RSS:
https://你的域名/feed插件会自动接管 Typecho 原生的 /feed 路由,使用美化后的 RSS 输出。
将上述任一地址复制到您的 RSS 阅读器(如 Feedly、Inoreader 等)中即可订阅。
直接在浏览器中打开上述地址,将看到美化后的 RSS 页面。根据你在插件设置中选择的主题,会显示不同风格:
#667eea → #764ba2#000 + #fff + #666#0066cc → #0052a3#0d1117 + #58a6ff/feed 路由Rss/
├── Plugin.php # 插件主文件
├── Action.php # RSS 生成逻辑
├── rss-gradient.xsl # 渐变风格样式
├── rss-minimal.xsl # 简洁风格样式
├── rss-modern.xsl # 现代风格样式
├── rss-dark.xsl # 暗黑风格样式
└── README.md # 说明文档A: 请检查您的 Typecho 是否开启了路由重写功能。
A: 请确保文章中的图片使用的是绝对路径。
A: 在插件设置中将"RSS 样式美化"设置为"禁用"即可。
A: 可以,直接修改 rss.xsl 文件中的 CSS 部分。
本插件遵循 MIT 许可证开源。
如有问题或建议,欢迎通过以下方式反馈:
享受优雅的 RSS 订阅体验!
本文由 老孙博客 原创发布
转载请注明出处
本主题移植自开源项目
https://github.com/Licoy/wordpress-theme-puock
原项目为WordPress主题.
我觉得很酷 所以移植到了Typecho 平台.
本站
适用于Typecho 1.2.0 以上版本
[danger]注意事项[/danger]
部分功能需要使用插件Puock来实现
插件地址 Puock
https://github.com/jkjoy/Typecho-Theme-Puock
本文由 老孙博客 原创发布
转载请注明出处
在内存爆满几乎100%的情况下,开启了swap分区却占用一直为0,这种情况发生在阿里云 和 华为云 比较多.
查看系统的 Swap 使用倾向(swappiness),执行
cat /proc/sys/vm/swappiness最小为0最大为100 数字越小代表使用倾向越低.
我的华为云服务器查询到的数值竟然为0. 这意味着云服务商根本就不打算让人使用swap.
编辑配置
sudo nano /etc/sysctl.conf找到
vm.swappiness=把后面的数字改为90
如果没有找到,也没关系,直接在最后添加
vm.swappiness=90然后执行
sudo sysctl -p可以生效了
本文由 老孙博客 原创发布
转载请注明出处
昨天是我妈生日,今天是我的生日.
本来我都已经忘记了,但是在那个相亲相爱一家人的群里总有人会记得大家的生日.
小时候是很爱过生日的,总会在生日当天叫上自己的小伙伴来自家玩耍.
少年时候也是喜欢过生日的,无非是找个由头和自己喜欢的女孩子在一起玩耍.
如今却是不喜欢的,无疑是告诉自己又年长一岁.又老了一些.
总是会有些负面的情绪是因为太过于繁琐和无奈.
社会总是会磨平年少的棱角.
也会产生更多的怨气.
人生有七苦,生、老、病、死、怨憎会、爱别离、求不得.
终一世无法超脱.
Ring的父亲由于癌细胞恶化现在在医院里等待最后的时光.她在医院衣不解带伺候了好几日.
我无法安慰.
这就是人生之中难言必经的阵痛.
有时候会思考生活的意义.
最后觉得有人说的其实很对,无非就是生下来,活下去.
活着其实没有任何的意义,这一辈子不过是追求欲望的过程.
也是与自己不停和解的过程.
我想只要是人,都无法超脱.
想说的很多,不足为外人道哉.
祝自己生日快乐吧
本文由 老孙博客 原创发布
转载请注明出处
## 写在前面
众所周知Typecho的插件多且杂.各种版本分支满天飞.
这里收集一些我自用的插件.
供同样使用Typecho的博友们下载使用
很多主题友情链接页面都是基于此插件来实现,当然也有很多主题不使用此插件.
或者没有友情链接页面的主题亦可以使用此插件来实现
兼容性测试: 兼容1.3.0
一个非官方的插件商店,里面的插件万年没更新...
ps.我准备等1.3.0正式版发布维护一个插件列表..
兼容性测试: 兼容1.3.0
由泽泽社长开发的插件,在编辑文章的页面展示所有的标签,点击即可添加
兼容性测试: 兼容1.3.0
同样是由泽泽社长开发的插件,在有新的评论或回复时会通知到对方邮箱
兼容性测试: 兼容1.3.0
网站地图 插件,这个自不必多说了.方便搜索引擎的收录.
这里收录的是 Google Sitemap 生成器 【TF社区维护版】
兼容性测试: 兼容1.3.0
一款使用AI生成文章摘要的插件.
使用AI的优点在于SEO
下载地址
兼容性测试: 兼容1.3.0
使用s3兼容的存储服务.支持腾讯云,阿里云,缤纷云,R2,B2 等
下载地址
同步博客标题 摘要 链接 到联邦宇宙.支持 Mastodon / Pleroma / Gotosocial / Misskey 等实例..
兼容性测试: 兼容1.3.0
评论QQ通知
通过基于nonebot 协议的QQ机器人,在有新评论时通知到指定QQ.
QQ与QQ机器人必须为好友.
兼容性测试: 兼容1.3.0
Typecho机器人提醒插件,支持登录提醒,支持评论提醒,支持钉钉、飞书、企微机器人。
本文由 老孙博客 原创发布
转载请注明出处
随便记录
由于Typecho更新了1.3.0 RC 于是我跟着更新了最新的版本
顺便把数据库从mysql换为sqlite,降低服务器使用资源.
容器依旧使用的是Docker镜像 jkjoy/php83
有很多插件失效或者报错,把报错信息告诉Deepseek他会告诉你是哪里出了问题,改了改就还能用.
对于父亲节亦或者母亲节不过是些无关紧要的名头.
我们不需要.
但是有些人需要.
父母和外公打算这个周末回老家.
小姨夫因为学校暑假,刚好也回老家住两个月.
小姨觉得一个人在天津也没事,遂决定回家看看.
二姨由于没有买到票不能跟外公一起回去.作罢.
我也已经有五六年没有回去了.
本来担心老爸的身体在家会有所不便,现在看来有这么多人在家照应,也放心不少.
养猫真的麻烦,怎么会有随地大小便的家伙!
但是养都养了能怎么办呢
本文由 老孙博客 原创发布
转载请注明出处
## 服务器端
使用Nginx作为web服务端可以使用以下开启
# 启用 gzip 压缩
gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_proxied any;
gzip_types
application/javascript
application/json
application/xml
text/css
text/plain
text/xml;
# 浏览器缓存控制
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 30d;
add_header Cache-Control "public, no-transform";
}使用原生js实现懒加载
document.addEventListener('DOMContentLoaded', function() {
// 获取所有图片(如果主题默认输出 src,可以动态替换为 data-src)
const images = document.querySelectorAll('img[src]:not([data-src])');
// 防止重复处理
images.forEach(img => {
if (!img.getAttribute('data-src')) {
img.setAttribute('data-src', img.src); // 把 src 存到 data-src
img.removeAttribute('src'); // 移除 src,避免立即加载
}
});
// 懒加载逻辑
const lazyLoad = (targets) => {
if ('IntersectionObserver' in window) {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img); // 加载后停止观察
}
});
});
targets.forEach(img => observer.observe(img));
} else {
// 兼容旧浏览器(滚动监听)
const checkImages = () => {
targets.forEach(img => {
const rect = img.getBoundingClientRect();
if (rect.top < window.innerHeight + 100) { // 提前 100px 加载
img.src = img.dataset.src;
}
});
};
window.addEventListener('scroll', checkImages);
checkImages(); // 初始检查
}
};
// 对所有 data-src 图片应用懒加载
lazyLoad(document.querySelectorAll('img[data-src]'));
});压缩HTML输出
// 在主题的 functions.php 中
function compress_html($html) {
$placeholders = [];
$i = 0;
// 匹配 <pre> 和 <code> 区块,替换为唯一占位符
$html = preg_replace_callback(
'/<(pre|code)[^>]*>.*?<\/\1>/is',
function ($matches) use (&$placeholders, &$i) {
$key = "###HTML_COMPRESS_IGNORE_" . $i . "###";
$placeholders[$key] = $matches[0];
$i++;
return $key;
},
$html
);
// 正常压缩
$search = array(
'/\>[^\S ]+/s',
'/[^\S ]+\</s',
'/(\s)+/s'
);
$replace = array(
'>',
'<',
'\\1'
);
$html = preg_replace($search, $replace, $html);
// 恢复占位符
if (!empty($placeholders)) {
$html = str_replace(array_keys($placeholders), array_values($placeholders), $html);
}
return $html;
}//在head.php中开启
<?php ob_start("compress_html"); ?>//在footer.php中结束
<?php ob_end_flush(); ?><meta http-equiv="Cache-Control" content="max-age=86400" />在header.php中加入
在主题的 functions.php 中添加简单的页面缓存
function page_cache() {
// 不缓存后台、登录页和提交操作
if (defined('__TYPECHO_ADMIN__') || $_SERVER['REQUEST_METHOD'] != 'GET') {
return;
}
$cache_dir = __TYPECHO_ROOT_DIR__ . '/cache';
if (!is_dir($cache_dir)) {
mkdir($cache_dir, 0755, true);
}
$url_hash = md5($_SERVER['REQUEST_URI']);
$cache_file = $cache_dir . '/' . $url_hash . '.html';
// 缓存过期时间(秒)
$cache_time = 3600; // 1小时
// 如果缓存文件存在且未过期,则直接输出
if (file_exists($cache_file) && (time() - filemtime($cache_file) < $cache_time)) {
echo file_get_contents($cache_file);
exit;
}
// 否则开始输出缓冲
ob_start();
}
function save_cache() {
// 同样跳过后台等页面
if (defined('__TYPECHO_ADMIN__') || $_SERVER['REQUEST_METHOD'] != 'GET') {
return;
}
$cache_dir = __TYPECHO_ROOT_DIR__ . '/cache';
$url_hash = md5($_SERVER['REQUEST_URI']);
$cache_file = $cache_dir . '/' . $url_hash . '.html';
// 保存缓冲内容到文件
$content = ob_get_contents();
file_put_contents($cache_file, $content);
}// 在页面开始处调用
page_cache();// 在页面结束前调用
register_shutdown_function('save_cache');Impact-Site-Verification: 52b4ec5d-e5be-481e-ad19-28bba5ed5c58
本文由 老孙博客 原创发布
转载请注明出处
## 置顶
最近才发现原来用的文章置顶还是有些问题的,置顶N篇文章,翻页时文章列表中文章还是维持原有的翻页逻辑,那么列表中文章的总数中就会减少N篇文章.
正常逻辑应该是从文章列表中查找需要置顶的文章展示在首页列表,把原有的文章列表向后压.翻页时会筛选已经置顶的文章.不再重复显示.
在index.php中插入代码
<?php
$sticky = $this->options->sticky;
$db = Typecho_Db::get();
$pageSize = $this->options->pageSize;
if ($sticky && !empty(trim($sticky))) {
$sticky_cids = array_filter(explode('|', $sticky));
if (!empty($sticky_cids)) {
$sticky_html = " <span class='sticky--post'><svg xmlns='http://www.w3.org/2000/svg' width='16px' height='16px' fill='none' viewBox='0 0 24 24' class='bk'>
<path fill='#242424' fill-rule='evenodd' d='M12.333 16.993a7.4 7.4 0 0 1-1.686-.12 7.25 7.25 0 1 1 8.047-4.334v.001a7.2 7.2 0 0 1-.632 1.188 7.26 7.26 0 0 1-4.708 3.146l-.07.013q-.466.083-.951.105m.356.979a8.4 8.4 0 0 1-1.377 0l-2.075 5.7a.375.375 0 0 1-.625.13l-2.465-2.604-3.563.41a.375.375 0 0 1-.395-.501l2.645-7.267a8.25 8.25 0 1 1 14.333 0l2.645 7.267a.375.375 0 0 1-.396.5l-3.562-.41-2.465 2.604a.375.375 0 0 1-.625-.13zm5.786-3.109a8.25 8.25 0 0 1-4.775 2.962l1.658 4.554 1.77-1.87.344-.362.496.057 2.558.294zm-12.95 0L3.476 20.5l2.557-.295.497-.057.344.363 1.77 1.87 1.658-4.555a8.25 8.25 0 0 1-4.775-2.961' clip-rule='evenodd'></path></svg></span> ";
// 保存原始对象状态
$originalRows = $this->row;
$originalStack = $this->stack;
$originalLength = $this->length;
$totalOriginal = $this->getTotal();
// 重置当前对象状态
$this->row = [];
$this->stack = [];
$this->length = 0;
if (isset($this->currentPage) && $this->currentPage == 1) {
// 查询置顶文章
$selectSticky = $this->select()->where('type = ?', 'post');
foreach ($sticky_cids as $i => $cid) {
if ($i == 0)
$selectSticky->where('cid = ?', $cid);
else
$selectSticky->orWhere('cid = ?', $cid);
}
$stickyPosts = $db->fetchAll($selectSticky);
// 添加置顶文章到结果集
foreach ($stickyPosts as &$stickyPost) {
$stickyPost['isSticky'] = true;
$stickyPost['stickyHtml'] = $sticky_html;
$this->push($stickyPost);
}
// 计算当前页应显示的普通文章数量
$standardPageSize = $pageSize - count($stickyPosts);
// 确保第一页不会显示太多文章
if ($standardPageSize <= 0) {
$standardPageSize = 0; // 如果置顶文章已经填满或超过一页,则不显示普通文章
}
} else {
// 非第一页显示正常数量的文章
$standardPageSize = $pageSize;
}
// 查询普通文章
if ($this->currentPage == 1) {
// 第一页需要排除置顶文章并限制数量
$selectNormal = $this->select()
->where('type = ?', 'post')
->where('status = ?', 'publish')
->where('created < ?', time());
// 排除所有置顶文章
foreach ($sticky_cids as $cid) {
$selectNormal->where('table.contents.cid != ?', $cid);
}
$selectNormal->order('created', Typecho_Db::SORT_DESC)
->limit($standardPageSize)
->offset(0);
} else {
// 非第一页的查询
// 计算正确的偏移量:(当前页码-1) * 每页数量 - 置顶文章数
// 这样可以确保不会漏掉文章
$offset = ($this->currentPage - 1) * $pageSize - count($sticky_cids);
$offset = max($offset, 0); // 确保偏移量不为负
$selectNormal = $this->select()
->where('type = ?', 'post')
->where('status = ?', 'publish')
->where('created < ?', time());
// 排除所有置顶文章
foreach ($sticky_cids as $cid) {
$selectNormal->where('table.contents.cid != ?', $cid);
}
$selectNormal->order('created', Typecho_Db::SORT_DESC)
->limit($pageSize)
->offset($offset);
}
} else {
// 没有有效的置顶文章ID,正常查询
$selectNormal = $this->select()
->where('type = ?', 'post')
->where('status = ?', 'publish')
->where('created < ?', time())
->order('created', Typecho_Db::SORT_DESC)
->page(isset($this->currentPage) ? $this->currentPage : 1, $pageSize);
}
} else {
// 没有设置置顶文章,正常查询
$selectNormal = $this->select()
->where('type = ?', 'post')
->where('status = ?', 'publish')
->where('created < ?', time())
->order('created', Typecho_Db::SORT_DESC)
->page(isset($this->currentPage) ? $this->currentPage : 1, $pageSize);
}
// 添加私有文章查询条件
if ($this->user->hasLogin()) {
$uid = $this->user->uid;
if ($uid) {
$selectNormal->orWhere('authorId = ? AND status = ?', $uid, 'private');
}
}
// 获取普通文章
$normalPosts = $db->fetchAll($selectNormal);
// 如果没有置顶文章或在前面的代码中没有重置对象状态,则在这里重置
if (empty($sticky) || empty(trim($sticky)) || empty($sticky_cids)) {
$this->row = [];
$this->stack = [];
$this->length = 0;
}
// 将普通文章添加到结果集
foreach ($normalPosts as $normalPost) {
$this->push($normalPost);
}
?>在主题的functions.php文件中查找
function themeConfig($form) {之后插入代码,会增加一个主题设置项
$sticky = new Typecho_Widget_Helper_Form_Element_Text('sticky', NULL, NULL, _t('置顶文章cid'), _t('多篇文章以`|`符号隔开'), _t('展示需要置顶的文章。'));
$form->addInput($sticky);在文章列表中合适的地方插入
<?php if (isset($this->isSticky) && $this->isSticky): ?>
<?php echo $this->stickyHtml; ?>
<?php endif; ?>本文由 老孙博客 原创发布
转载请注明出处
使用穷举的方式来匹配自定义icon
根据分类的slug来匹配
<?php
switch($categories->slug) {
case 'images': echo '<i class="bi bi-images me-1"></i>';
break;
case 'share': echo '<i class="bi bi-share-fill me-1"></i>';
break;
case 'NULL': echo '<i class="bi bi-speaker-fill me-1"></i>';
break;
case 'memos': echo '<i class="bi bi-chat me-1"></i>';
break;
case 'codes': echo '<i class="bi bi-code me-1"></i>';
break;
case 'logs': echo '<i class="bi bi-person-fill me-1"></i>';
break;
case 'test': echo '<i class="bi bi-calendar-fill me-1"></i>';
break;
case 'tools': echo '<i class="bi bi-tools me-1"></i>';
break;
case 'music': echo '<i class="bi bi-music-note me-1"></i>';
break;
case 'links': echo '<i class="bi bi-link me-1"></i>';
break;
case 'video': echo '<i class="bi bi-camera-video me-1"></i>';
break;
case 'life': echo '<i class="bi bi-heart-fill me-1"></i>';
break;
case 'study': echo '<i class="bi bi-book-fill me-1"></i>';
break;
case 'news': echo '<i class="bi bi-newspaper me-1"></i>';
break;
case 'themes': echo '<i class="bi bi-palette me-1"></i>';
break;
case 'plugins': echo '<i class="bi bi-gear-fill me-1"></i>';
break;
case 'photo': echo '<i class="bi bi-images me-1"></i>';
break;
default: echo '<i class="bi bi-folder-fill me-1"></i>';
} ?>
同样也可以根据自定义页面的slug匹配
<?php $pages = Typecho_Widget::widget('Widget_Contents_Page_List'); ?>
<?php while($pages->next()): ?>
<li>
<a href="<?php $pages->permalink(); ?>">
<?php
switch($pages->slug) {
case 'about': echo '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-user" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><circle cx="12" cy="7" r="4" /><path d="M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2" /></svg> '; // 关于页面
break;
case 'links': echo '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-link" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><path d="M10 14a3.5 3.5 0 0 0 5 0l4 -4a3.5 3.5 0 0 0 -5 -5l-.5 .5" /><path d="M14 10a3.5 3.5 0 0 0 -5 0l-4 4a3.5 3.5 0 0 0 5 5l.5 -.5" /></svg>'; // 链接页面
break;
case 'archives': echo '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-archive" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><rect x="3" y="4" width="18" height="4" rx="2" /><path d="M5 8v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-10" /><line x1="10" y1="12" x2="14" y2="12" /></svg>'; // 归档页面
break;
case 'gbook': echo '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-article" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-article"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 4m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" /><path d="M7 8h10" /><path d="M7 12h10" /><path d="M7 16h10" /></svg>'; // 博客页面
break;
case 'messages': echo '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-messages" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-messages"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M21 14l-3 -3h-7a1 1 0 0 1 -1 -1v-6a1 1 0 0 1 1 -1h9a1 1 0 0 1 1 1v10" /><path d="M14 15v2a1 1 0 0 1 -1 1h-7l-3 3v-10a1 1 0 0 1 1 -1h2" /></svg>'; // 留言页面
break;
default: echo '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-file" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-file"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M14 3v4a1 1 0 0 0 1 1h4" /><path d="M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2z" /></svg>'; // 默认图标
} ?>
<span><?php $pages->title(); ?></span>
</a>
</li>
<?php endwhile; ?>本文由 老孙博客 原创发布
转载请注明出处
## 起因
看了@1900 长毛象新通知推送TGBot
深受启发,于是通过ChatGPT 4.1 写了一个python脚本
定时检测Mastodon消息,有新通知会通过QQbot的API URL 发送消息通知给指定的QQ用户(Nonebot兼容)
构建了一个docker镜像jkjoy/mastodon2qqbot
代码仓库在 https://github.com/jkjoy/dockerfile/blob/main/mastodon2qqbot/main.py
使用docker run 命令启动
docker run -d \
-e MASTODON_INSTANCE="https://你的mastodon实例" \
-e MASTODON_TOKEN="你的token" \
-e QQ_API="https://bot.0tz.top/send_private_msg" \
-e QQ_ID="你的QQ号码" \
-e CHECK_INTERVAL="30" \
jkjoy/mastodon2qqbot30秒检查一次 Mastodon 消息
通过默认的QQ机器人2280858259 发送消息给我使用的QQ80116747
默认的QQ API 是https://bot.0tz.top/send_private_msg
使用默认QQ API需要添加QQ机器人2280858259为好友
本文由 老孙博客 原创发布
转载请注明出处
在文章的附件选项页加入批量插入所有附件的按钮
并自动识别图片与普通文件,实现图片预览功能
![]()
Markdown语法格式自动修正为
具体代码实现
在主题的functions.php最后插入
/**
* Typecho后台附件增强:图片预览、批量插入、保留官方删除按钮与逻辑
* @author jkjoy
* @date 2025-04-25
*/
Typecho_Plugin::factory('admin/write-post.php')->bottom = array('AttachmentHelper', 'addEnhancedFeatures');
Typecho_Plugin::factory('admin/write-page.php')->bottom = array('AttachmentHelper', 'addEnhancedFeatures');
class AttachmentHelper {
public static function addEnhancedFeatures() {
?>
<style>
#file-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:15px;padding:15px;list-style:none;margin:0;}
#file-list li{position:relative;border:1px solid #e0e0e0;border-radius:4px;padding:10px;background:#fff;transition:all 0.3s ease;list-style:none;margin:0;}
#file-list li:hover{box-shadow:0 2px 8px rgba(0,0,0,0.1);}
#file-list li.loading{opacity:0.7;pointer-events:none;}
.att-enhanced-thumb{position:relative;width:100%;height:150px;margin-bottom:8px;background:#f5f5f5;overflow:hidden;border-radius:3px;display:flex;align-items:center;justify-content:center;}
.att-enhanced-thumb img{width:100%;height:100%;object-fit:contain;display:block;}
.att-enhanced-thumb .file-icon{display:flex;align-items:center;justify-content:center;width:100%;height:100%;font-size:40px;color:#999;}
.att-enhanced-finfo{padding:5px 0;}
.att-enhanced-fname{font-size:13px;margin-bottom:5px;word-break:break-all;color:#333;}
.att-enhanced-fsize{font-size:12px;color:#999;}
.att-enhanced-factions{display:flex;justify-content:space-between;align-items:center;margin-top:8px;gap:8px;}
.att-enhanced-factions button{flex:1;padding:4px 8px;border:none;border-radius:3px;background:#e0e0e0;color:#333;cursor:pointer;font-size:12px;transition:all 0.2s ease;}
.att-enhanced-factions button:hover{background:#d0d0d0;}
.att-enhanced-factions .btn-insert{background:#467B96;color:white;}
.att-enhanced-factions .btn-insert:hover{background:#3c6a81;}
.att-enhanced-checkbox{position:absolute;top:5px;right:5px;z-index:2;width:18px;height:18px;cursor:pointer;}
.batch-actions{margin:15px;display:flex;gap:10px;align-items:center;}
.btn-batch{padding:8px 15px;border-radius:4px;border:none;cursor:pointer;transition:all 0.3s ease;font-size:10px;display:inline-flex;align-items:center;justify-content:center;}
.btn-batch.primary{background:#467B96;color:white;}
.btn-batch.primary:hover{background:#3c6a81;}
.btn-batch.secondary{background:#e0e0e0;color:#333;}
.btn-batch.secondary:hover{background:#d0d0d0;}
.upload-progress{position:absolute;bottom:0;left:0;width:100%;height:2px;background:#467B96;transition:width 0.3s ease;}
</style>
<script>
$(document).ready(function() {
// 批量操作UI按钮
var $batchActions = $('<div class="batch-actions"></div>')
.append('<button type="button" class="btn-batch primary" id="batch-insert">批量插入</button>')
.append('<button type="button" class="btn-batch secondary" id="select-all">全选</button>')
.append('<button type="button" class="btn-batch secondary" id="unselect-all">取消全选</button>');
$('#file-list').before($batchActions);
// 插入格式
Typecho.insertFileToEditor = function(title, url, isImage) {
var textarea = $('#text'),
sel = textarea.getSelection(),
insertContent = isImage ? '' :
'[' + title + '](' + url + ')';
textarea.replaceSelection(insertContent + '\n');
textarea.focus();
};
// 批量插入
$('#batch-insert').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
var content = '';
$('#file-list li').each(function() {
if ($(this).find('.att-enhanced-checkbox').is(':checked')) {
var $li = $(this);
var title = $li.find('.att-enhanced-fname').text();
var url = $li.data('url');
var isImage = $li.data('image') == 1;
content += isImage ? '\n' : '[' + title + '](' + url + ')\n';
}
});
if (content) {
var textarea = $('#text');
var pos = textarea.getSelection();
var newContent = textarea.val();
newContent = newContent.substring(0, pos.start) + content + newContent.substring(pos.end);
textarea.val(newContent);
textarea.focus();
}
});
$('#select-all').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
$('#file-list .att-enhanced-checkbox').prop('checked', true);
return false;
});
$('#unselect-all').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
$('#file-list .att-enhanced-checkbox').prop('checked', false);
return false;
});
// 防止复选框冒泡
$(document).on('click', '.att-enhanced-checkbox', function(e) {e.stopPropagation();});
// 增强文件列表样式,但不破坏li原结构和官方按钮
function enhanceFileList() {
$('#file-list li').each(function() {
var $li = $(this);
if ($li.hasClass('att-enhanced')) return;
$li.addClass('att-enhanced');
// 只增强,不清空li
// 增加批量选择框
if ($li.find('.att-enhanced-checkbox').length === 0) {
$li.prepend('<input type="checkbox" class="att-enhanced-checkbox" />');
}
// 增加图片预览(如已有则不重复加)
if ($li.find('.att-enhanced-thumb').length === 0) {
var url = $li.data('url');
var isImage = $li.data('image') == 1;
var fileName = $li.find('.insert').text();
var $thumbContainer = $('<div class="att-enhanced-thumb"></div>');
if (isImage) {
var $img = $('<img src="' + url + '" alt="' + fileName + '" />');
$img.on('error', function() {
$(this).replaceWith('<div class="file-icon">🖼️</div>');
});
$thumbContainer.append($img);
} else {
$thumbContainer.append('<div class="file-icon">📄</div>');
}
// 插到插入按钮之前
$li.find('.insert').before($thumbContainer);
}
});
}
// 插入按钮事件
$(document).on('click', '.btn-insert', function(e) {
e.preventDefault();
e.stopPropagation();
var $li = $(this).closest('li');
var title = $li.find('.att-enhanced-fname').text();
Typecho.insertFileToEditor(title, $li.data('url'), $li.data('image') == 1);
});
// 上传完成后增强新项
var originalUploadComplete = Typecho.uploadComplete;
Typecho.uploadComplete = function(attachment) {
setTimeout(function() {
enhanceFileList();
}, 200);
if (typeof originalUploadComplete === 'function') {
originalUploadComplete(attachment);
}
};
// 首次增强
enhanceFileList();
});
</script>
<?php
}
}
?>本文由 老孙博客 原创发布
转载请注明出处
作为一个在广东生活了十几年的人来说,没去过香港算不算得上是一件很稀罕的事儿呢?
![]()
不得不说,香港的物价真的离谱.
![]()
![]()
游客贼多,摩天轮都要排队两小时
![]()
天桥下,地下通道出入口很多菲律宾人
![]()
坐摆渡轮也排队一个小时
![]()
![]()
![]()
![]()
![]()
![]()
本文由 老孙博客 原创发布
转载请注明出处
由于最近QQ风控似乎更加严格了,有两个QQ号码被封号,还有一个经常掉线.
为了检测QQ机器人是否在线 我就让 Claude 3.5 写了一个定时发送消息的 python 程序
单纯只发个消息未免 单调,于是 就调用高德地图 的 天气查询 APi 查询 指定地点的天气情况
于是 实现了 指定QQ机器人 给 指定 QQ用户 指定频率 发送 天气情况的功能
![]()
我真是太机智了,一下子就解决了 两个问题
Docker镜像 jkjoy/qq-weather-bot
本文由 老孙博客 原创发布
转载请注明出处
本来日子平平淡淡.没有波澜.
什么也不想写.
把主题更新了一下,把之前没有完善的功能完善一番,优化了部分功能的实现方式
重新移植了一个主题,就是因为上班摸鱼随便弄着玩.
前两天荆门市宣传部突然打电话找我,说我已备案的网站上出现了不合规定的内容责令我删除.
主要内容有两处:
vmess科学上网的内容ChatGPT相关内容翻墙我可以理解,但是chatgpt 为啥就不行呢
我好奇问了一句
给我回复是,chatgpt 国内能用不?是不是得翻墙?
此话一出我竟无言以对.
于是删除了相关页面.
本以为这事情就完事了
昨天,又打电话来询问.
问能不能提供文章删除的证明.
我说文章都已经删除了,怎么还需要我证明我已经删除了呢?
你访问不到不就证明我删除了么
本文由 老孙博客 原创发布
转载请注明出处
## 简介
这是一款从wordpress 移植的双栏主题
原作者是 绘主题
使用友情链接 必须使用插件Links
![]()
assets/img目录下,后缀名为.png| 页面slug | icon |
|---|---|
| memos | 📝 |
| links | 🔗 |
| about | 👤 |
| tags | 🏷 |
| categories | 📂 |
| search | 🔍 |
| archives | 📜 |
| comments | 💬 |
| help | ❓ |
| gbook | 📖 |
| 分类slug | icon |
|---|---|
| images | |
| share | |
| NULL | |
| memos | |
| codes | |
| logs | |
| test | |
| tools | |
| music | |
| links | |
| video | |
| life | |
| study | |
| news | |
| themes | |
| plugins | |
| photo | |
| default |
初始版本
https://github.com/jkjoy/Typecho-Theme-Mango
本文由 老孙博客 原创发布
转载请注明出处
## 起因
想部署一个简单的邮件服务器 抛弃各种免费的企业邮局
主要是因为有太多账号都已经忘记了,而找回账号又是一阵折腾.
services:
mailserver:
image: ghcr.io/docker-mailserver/docker-mailserver
container_name: mailserver
#env_file: ./.env
healthcheck:
retries: 0
test: ss --listening --tcp | grep -P 'LISTEN.+:smtp' || exit 1
timeout: 3s
hostname: mail.imsun.org
ports:
- 25:25
- 143:143
- 465:465
- 587:587
- 993:993
restart: always
stop_grace_period: 1m
volumes:
- ./data/dms/mail-data/:/var/mail/
- ./data/dms/mail-state/:/var/mail-state/
- ./data/dms/mail-logs/:/var/log/mail/
- ./data/dms/config/:/tmp/docker-mailserver/
- /etc/localtime:/etc/localtime:ro25 143 465 587 993启动容器之后
在容器中执行
setup email add admin@example.com输入密码
重复确认密码
以example.com域名为例
为域名添加DNS记录
A 记录 mail 指向 服务器IPMX 记录 @ 指向 mail.example.comPTR 记录 @ 指向 mail.example.com
4.21 更新 google 会提示拒收邮件..原因是没有验证SPF or DKIM
添加TXT记录@指向 v=spf1 a mx ip4:74.48.*.* ~all
如此便可以使用smtp发送邮件了
SMTP 地址 mail.example.com
账户 : admin@example.com
密码 :
端口 : 587
当然可以使用webmail客户端 roundcube 来进行收发邮件
使用Docker compose来快速部署
services:
roundcubemail:
image: roundcube/roundcubemail:1.6.9-apache
volumes:
- ./www:/var/www/html
- ./db/sqlite:/var/roundcube/db
environment:
- ROUNDCUBEMAIL_DB_TYPE=sqlite
- ROUNDCUBEMAIL_SKIN=elastic
- ROUNDCUBEMAIL_DEFAULT_HOST=tls://mail.example.com
- ROUNDCUBEMAIL_SMTP_SERVER=tls://mail.example.com
ports:
- 8080:80mail.example.com 域名mail.example.com配置好SSL证书访问 mail.example.com ,输入 账号密码 登录即可进行收发邮件了.
# -----------------------------------------------
# --- Mailserver Environment Variables ----------
# -----------------------------------------------
# DOCUMENTATION FOR THESE VARIABLES IS FOUND UNDER
# https://docker-mailserver.github.io/docker-mailserver/latest/config/environment/
# -----------------------------------------------
# --- General Section ---------------------------
# -----------------------------------------------
# empty => uses the `hostname` command to get the mail server's canonical hostname
# => Specify a fully-qualified domainname to serve mail for. This is used for many of the config features so if you can't set your hostname (e.g. you're in a container platform that doesn't let you) specify it in this environment variable.
OVERRIDE_HOSTNAME=
# REMOVED in version v11.0.0! Use LOG_LEVEL instead.
DMS_DEBUG=0
# Set the log level for DMS.
# This is mostly relevant for container startup scripts and change detection event feedback.
#
# Valid values (in order of increasing verbosity) are: `error`, `warn`, `info`, `debug` and `trace`.
# The default log level is `info`.
LOG_LEVEL=info
# critical => Only show critical messages
# error => Only show erroneous output
# **warn** => Show warnings
# info => Normal informational output
# debug => Also show debug messages
SUPERVISOR_LOGLEVEL=
# 0 => mail state in default directories
# 1 => consolidate all states into a single directory (`/var/mail-state`) to allow persistence using docker volumes
ONE_DIR=1
# Support for deployment where these defaults are not compatible (eg: some NAS appliances):
# /var/mail vmail User ID (default: 5000)
DMS_VMAIL_UID=
# /var/mail vmail Group ID (default: 5000)
DMS_VMAIL_GID=
# **empty** => use FILE
# LDAP => use LDAP authentication
# OIDC => use OIDC authentication (not yet implemented)
# FILE => use local files (this is used as the default)
ACCOUNT_PROVISIONER=
# empty => postmaster@domain.com
# => Specify the postmaster address
POSTMASTER_ADDRESS=
# Check for updates on container start and then once a day
# If an update is available, a mail is sent to POSTMASTER_ADDRESS
# 0 => Update check disabled
# 1 => Update check enabled
ENABLE_UPDATE_CHECK=1
# Customize the update check interval.
# Number + Suffix. Suffix must be 's' for seconds, 'm' for minutes, 'h' for hours or 'd' for days.
UPDATE_CHECK_INTERVAL=1d
# Set different options for mynetworks option (can be overwrite in postfix-main.cf)
# **WARNING**: Adding the docker network's gateway to the list of trusted hosts, e.g. using the `network` or
# `connected-networks` option, can create an open relay
# https://github.com/docker-mailserver/docker-mailserver/issues/1405#issuecomment-590106498
# The same can happen for rootless podman. To prevent this, set the value to "none" or configure slirp4netns
# https://github.com/docker-mailserver/docker-mailserver/issues/2377
#
# none => Explicitly force authentication
# container => Container IP address only
# host => Add docker container network (ipv4 only)
# network => Add all docker container networks (ipv4 only)
# connected-networks => Add all connected docker networks (ipv4 only)
PERMIT_DOCKER=none
# Set the timezone. If this variable is unset, the container runtime will try to detect the time using
# `/etc/localtime`, which you can alternatively mount into the container. The value of this variable
# must follow the pattern `AREA/ZONE`, i.e. of you want to use Germany's time zone, use `Europe/Berlin`.
# You can lookup all available timezones here: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List
TZ=
# In case you network interface differs from 'eth0', e.g. when you are using HostNetworking in Kubernetes,
# you can set NETWORK_INTERFACE to whatever interface you want. This interface will then be used.
# - **empty** => eth0
NETWORK_INTERFACE=
# empty => modern
# modern => Enables TLSv1.2 and modern ciphers only. (default)
# intermediate => Enables TLSv1, TLSv1.1 and TLSv1.2 and broad compatibility ciphers.
TLS_LEVEL=
# Configures the handling of creating mails with forged sender addresses.
#
# **0** => (not recommended) Mail address spoofing allowed. Any logged in user may create email messages with a forged sender address (see also https://en.wikipedia.org/wiki/Email_spoofing).
# 1 => Mail spoofing denied. Each user may only send with his own or his alias addresses. Addresses with extension delimiters(http://www.postfix.org/postconf.5.html#recipient_delimiter) are not able to send messages.
SPOOF_PROTECTION=
# Enables the Sender Rewriting Scheme. SRS is needed if your mail server acts as forwarder. See [postsrsd](https://github.com/roehling/postsrsd/blob/master/README.md#sender-rewriting-scheme-crash-course) for further explanation.
# - **0** => Disabled
# - 1 => Enabled
ENABLE_SRS=0
# Enables the OpenDKIM service.
# **1** => Enabled
# 0 => Disabled
ENABLE_OPENDKIM=1
# Enables the OpenDMARC service.
# **1** => Enabled
# 0 => Disabled
ENABLE_OPENDMARC=1
# Enabled `policyd-spf` in Postfix's configuration. You will likely want to set this
# to `0` in case you're using Rspamd (`ENABLE_RSPAMD=1`).
#
# - 0 => Disabled
# - **1** => Enabled
ENABLE_POLICYD_SPF=1
# 1 => Enables POP3 service
# empty => disables POP3
ENABLE_POP3=
# Enables ClamAV, and anti-virus scanner.
# 1 => Enabled
# **0** => Disabled
ENABLE_CLAMAV=0
# Enables Rspamd
# **0** => Disabled
# 1 => Enabled
ENABLE_RSPAMD=0
# When `ENABLE_RSPAMD=1`, an internal Redis instance is enabled implicitly.
# This setting provides an opt-out to allow using an external instance instead.
# 0 => Disabled
# 1 => Enabled
ENABLE_RSPAMD_REDIS=
# When enabled,
#
# 1. the "[autolearning][rspamd-autolearn]" feature is turned on;
# 2. the Bayes classifier will be trained when moving mails from or to the Junk folder (with the help of Sieve scripts).
#
# **0** => disabled
# 1 => enabled
RSPAMD_LEARN=0
# This settings controls whether checks should be performed on emails coming
# from authenticated users (i.e. most likely outgoing emails). The default value
# is `0` in order to align better with SpamAssassin. We recommend reading
# through https://rspamd.com/doc/tutorials/scanning_outbound.html though to
# decide for yourself whether you need and want this feature.
RSPAMD_CHECK_AUTHENTICATED=0
# Controls whether the Rspamd Greylisting module is enabled.
# This module can further assist in avoiding spam emails by greylisting
# e-mails with a certain spam score.
#
# **0** => disabled
# 1 => enabled
RSPAMD_GREYLISTING=0
# Can be used to enable or disable the Hfilter group module.
#
# - 0 => Disabled
# - **1** => Enabled
RSPAMD_HFILTER=1
# Can be used to control the score when the HFILTER_HOSTNAME_UNKNOWN symbol applies. A higher score is more punishing. Setting it to 15 is equivalent to rejecting the email when the check fails.
#
# Default: 6
RSPAMD_HFILTER_HOSTNAME_UNKNOWN_SCORE=6
# Amavis content filter (used for ClamAV & SpamAssassin)
# 0 => Disabled
# 1 => Enabled
ENABLE_AMAVIS=1
# -1/-2/-3 => Only show errors
# **0** => Show warnings
# 1/2 => Show default informational output
# 3/4/5 => log debug information (very verbose)
AMAVIS_LOGLEVEL=0
# This enables DNS block lists in Postscreen.
# Note: Emails will be rejected, if they don't pass the block list checks!
# **0** => DNS block lists are disabled
# 1 => DNS block lists are enabled
ENABLE_DNSBL=0
# If you enable Fail2Ban, don't forget to add the following lines to your `compose.yaml`:
# cap_add:
# - NET_ADMIN
# Otherwise, `nftables` won't be able to ban IPs.
ENABLE_FAIL2BAN=0
# Fail2Ban blocktype
# drop => drop packet (send NO reply)
# reject => reject packet (send ICMP unreachable)
FAIL2BAN_BLOCKTYPE=drop
# 1 => Enables Managesieve on port 4190
# empty => disables Managesieve
ENABLE_MANAGESIEVE=
# **enforce** => Allow other tests to complete. Reject attempts to deliver mail with a 550 SMTP reply, and log the helo/sender/recipient information. Repeat this test the next time the client connects.
# drop => Drop the connection immediately with a 521 SMTP reply. Repeat this test the next time the client connects.
# ignore => Ignore the failure of this test. Allow other tests to complete. Repeat this test the next time the client connects. This option is useful for testing and collecting statistics without blocking mail.
POSTSCREEN_ACTION=enforce
# empty => all daemons start
# 1 => only launch postfix smtp
SMTP_ONLY=
# Please read [the SSL page in the documentation](https://docker-mailserver.github.io/docker-mailserver/latest/config/security/ssl) for more information.
#
# empty => SSL disabled
# letsencrypt => Enables Let's Encrypt certificates
# custom => Enables custom certificates
# manual => Let's you manually specify locations of your SSL certificates for non-standard cases
# self-signed => Enables self-signed certificates
SSL_TYPE=
# These are only supported with `SSL_TYPE=manual`.
# Provide the path to your cert and key files that you've mounted access to within the container.
SSL_CERT_PATH=
SSL_KEY_PATH=
# Optional: A 2nd certificate can be supported as fallback (dual cert support), eg ECDSA with an RSA fallback.
# Useful for additional compatibility with older MTA and MUA (eg pre-2015).
SSL_ALT_CERT_PATH=
SSL_ALT_KEY_PATH=
# Set how many days a virusmail will stay on the server before being deleted
# empty => 7 days
VIRUSMAILS_DELETE_DELAY=
# Configure Postfix `virtual_transport` to deliver mail to a different LMTP client (default is a dovecot socket).
# Provide any valid URI. Examples:
#
# empty => `lmtp:unix:/var/run/dovecot/lmtp` (default, configured in Postfix main.cf)
# `lmtp:unix:private/dovecot-lmtp` (use socket)
# `lmtps:inet:<host>:<port>` (secure lmtp with starttls)
# `lmtp:<kopano-host>:2003` (use kopano as mailstore)
POSTFIX_DAGENT=
# Set the mailbox size limit for all users. If set to zero, the size will be unlimited (default).
#
# empty => 0
POSTFIX_MAILBOX_SIZE_LIMIT=
# See https://docker-mailserver.github.io/docker-mailserver/edge/config/user-management/accounts/#notes
# 0 => Dovecot quota is disabled
# 1 => Dovecot quota is enabled
ENABLE_QUOTAS=1
# Set the message size limit for all users. If set to zero, the size will be unlimited (not recommended!)
#
# empty => 10240000 (~10 MB)
POSTFIX_MESSAGE_SIZE_LIMIT=
# Mails larger than this limit won't be scanned.
# ClamAV must be enabled (ENABLE_CLAMAV=1) for this.
#
# empty => 25M (25 MB)
CLAMAV_MESSAGE_SIZE_LIMIT=
# Enables regular pflogsumm mail reports.
# This is a new option. The old REPORT options are still supported for backwards compatibility. If this is not set and reports are enabled with the old options, logrotate will be used.
#
# not set => No report
# daily_cron => Daily report for the previous day
# logrotate => Full report based on the mail log when it is rotated
PFLOGSUMM_TRIGGER=
# Recipient address for pflogsumm reports.
#
# not set => Use REPORT_RECIPIENT or POSTMASTER_ADDRESS
# => Specify the recipient address(es)
PFLOGSUMM_RECIPIENT=
# Sender address (`FROM`) for pflogsumm reports if pflogsumm reports are enabled.
#
# not set => Use REPORT_SENDER
# => Specify the sender address
PFLOGSUMM_SENDER=
# Interval for logwatch report.
#
# none => No report is generated
# daily => Send a daily report
# weekly => Send a report every week
LOGWATCH_INTERVAL=
# Recipient address for logwatch reports if they are enabled.
#
# not set => Use REPORT_RECIPIENT or POSTMASTER_ADDRESS
# => Specify the recipient address(es)
LOGWATCH_RECIPIENT=
# Sender address (`FROM`) for logwatch reports if logwatch reports are enabled.
#
# not set => Use REPORT_SENDER
# => Specify the sender address
LOGWATCH_SENDER=
# Defines who receives reports if they are enabled.
# **empty** => ${POSTMASTER_ADDRESS}
# => Specify the recipient address
REPORT_RECIPIENT=
# Defines who sends reports if they are enabled.
# **empty** => mailserver-report@${DOMAINNAME}
# => Specify the sender address
REPORT_SENDER=
# Changes the interval in which log files are rotated
# **weekly** => Rotate log files weekly
# daily => Rotate log files daily
# monthly => Rotate log files monthly
#
# Note: This Variable actually controls logrotate inside the container
# and rotates the log files depending on this setting. The main log output is
# still available in its entirety via `docker logs mail` (Or your
# respective container name). If you want to control logrotation for
# the Docker-generated logfile see:
# https://docs.docker.com/config/containers/logging/configure/
#
# Note: This variable can also determine the interval for Postfix's log summary reports, see [`PFLOGSUMM_TRIGGER`](#pflogsumm_trigger).
LOGROTATE_INTERVAL=weekly
# If enabled, employs `reject_unknown_client_hostname` to sender restrictions in Postfix's configuration.
#
# - **0** => Disabled
# - 1 => Enabled
POSTFIX_REJECT_UNKNOWN_CLIENT_HOSTNAME=0
# Choose TCP/IP protocols for postfix to use
# **all** => All possible protocols.
# ipv4 => Use only IPv4 traffic. Most likely you want this behind Docker.
# ipv6 => Use only IPv6 traffic.
#
# Note: More details at http://www.postfix.org/postconf.5.html#inet_protocols
POSTFIX_INET_PROTOCOLS=all
# Choose TCP/IP protocols for dovecot to use
# **all** => Listen on all interfaces
# ipv4 => Listen only on IPv4 interfaces. Most likely you want this behind Docker.
# ipv6 => Listen only on IPv6 interfaces.
#
# Note: More information at https://dovecot.org/doc/dovecot-example.conf
DOVECOT_INET_PROTOCOLS=all
# -----------------------------------------------
# --- SpamAssassin Section ----------------------
# -----------------------------------------------
ENABLE_SPAMASSASSIN=0
# deliver spam messages in the inbox (eventually tagged using SA_SPAM_SUBJECT)
SPAMASSASSIN_SPAM_TO_INBOX=1
# KAM is a 3rd party SpamAssassin ruleset, provided by the McGrail Foundation.
# If SpamAssassin is enabled, KAM can be used in addition to the default ruleset.
# - **0** => KAM disabled
# - 1 => KAM enabled
#
# Note: only has an effect if `ENABLE_SPAMASSASSIN=1`
ENABLE_SPAMASSASSIN_KAM=0
# spam messages will be moved in the Junk folder (SPAMASSASSIN_SPAM_TO_INBOX=1 required)
MOVE_SPAM_TO_JUNK=1
# spam messages wil be marked as read
MARK_SPAM_AS_READ=0
# add spam info headers if at, or above that level:
SA_TAG=2.0
# add 'spam detected' headers at that level
SA_TAG2=6.31
# triggers spam evasive actions
SA_KILL=10.0
# add tag to subject if spam detected
SA_SPAM_SUBJECT=***SPAM*****
# -----------------------------------------------
# --- Fetchmail Section -------------------------
# -----------------------------------------------
ENABLE_FETCHMAIL=0
# The interval to fetch mail in seconds
FETCHMAIL_POLL=300
# Enable or disable `getmail`.
#
# - **0** => Disabled
# - 1 => Enabled
ENABLE_GETMAIL=0
# The number of minutes for the interval. Min: 1; Max: 30.
GETMAIL_POLL=5
# -----------------------------------------------
# --- LDAP Section ------------------------------
# -----------------------------------------------
# A second container for the ldap service is necessary (i.e. https://hub.docker.com/r/bitnami/openldap/)
# empty => no
# yes => LDAP over TLS enabled for Postfix
LDAP_START_TLS=
# empty => mail.example.com
# Specify the `<dns-name>` / `<ip-address>` where the LDAP server is reachable via a URI like: `ldaps://mail.example.com`.
# Note: You must include the desired URI scheme (`ldap://`, `ldaps://`, `ldapi://`).
LDAP_SERVER_HOST=
# empty => ou=people,dc=domain,dc=com
# => e.g. LDAP_SEARCH_BASE=dc=mydomain,dc=local
LDAP_SEARCH_BASE=
# empty => cn=admin,dc=domain,dc=com
# => take a look at examples of SASL_LDAP_BIND_DN
LDAP_BIND_DN=
# empty** => admin
# => Specify the password to bind against ldap
LDAP_BIND_PW=
# e.g. `"(&(mail=%s)(mailEnabled=TRUE))"`
# => Specify how ldap should be asked for users
LDAP_QUERY_FILTER_USER=
# e.g. `"(&(mailGroupMember=%s)(mailEnabled=TRUE))"`
# => Specify how ldap should be asked for groups
LDAP_QUERY_FILTER_GROUP=
# e.g. `"(&(mailAlias=%s)(mailEnabled=TRUE))"`
# => Specify how ldap should be asked for aliases
LDAP_QUERY_FILTER_ALIAS=
# e.g. `"(&(|(mail=*@%s)(mailalias=*@%s)(mailGroupMember=*@%s))(mailEnabled=TRUE))"`
# => Specify how ldap should be asked for domains
LDAP_QUERY_FILTER_DOMAIN=
# -----------------------------------------------
# --- Dovecot Section ---------------------------
# -----------------------------------------------
# empty => no
# yes => LDAP over TLS enabled for Dovecot
DOVECOT_TLS=
# e.g. `"(&(objectClass=PostfixBookMailAccount)(uniqueIdentifier=%n))"`
DOVECOT_USER_FILTER=
# e.g. `"(&(objectClass=PostfixBookMailAccount)(uniqueIdentifier=%n))"`
DOVECOT_PASS_FILTER=
# Define the mailbox format to be used
# default is maildir, supported values are: sdbox, mdbox, maildir
DOVECOT_MAILBOX_FORMAT=maildir
# empty => no
# yes => Allow bind authentication for LDAP
# https://wiki.dovecot.org/AuthDatabase/LDAP/AuthBinds
DOVECOT_AUTH_BIND=
# -----------------------------------------------
# --- Postgrey Section --------------------------
# -----------------------------------------------
ENABLE_POSTGREY=0
# greylist for N seconds
POSTGREY_DELAY=300
# delete entries older than N days since the last time that they have been seen
POSTGREY_MAX_AGE=35
# response when a mail is greylisted
POSTGREY_TEXT="Delayed by Postgrey"
# whitelist host after N successful deliveries (N=0 to disable whitelisting)
POSTGREY_AUTO_WHITELIST_CLIENTS=5
# -----------------------------------------------
# --- SASL Section ------------------------------
# -----------------------------------------------
ENABLE_SASLAUTHD=0
# empty => pam
# `ldap` => authenticate against ldap server
# `shadow` => authenticate against local user db
# `mysql` => authenticate against mysql db
# `rimap` => authenticate against imap server
# Note: can be a list of mechanisms like pam ldap shadow
SASLAUTHD_MECHANISMS=
# empty => None
# e.g. with SASLAUTHD_MECHANISMS rimap you need to specify the ip-address/servername of the imap server ==> xxx.xxx.xxx.xxx
SASLAUTHD_MECH_OPTIONS=
# empty => Use value of LDAP_SERVER_HOST
# Note: You must include the desired URI scheme (`ldap://`, `ldaps://`, `ldapi://`).
SASLAUTHD_LDAP_SERVER=
# empty => Use value of LDAP_BIND_DN
# specify an object with privileges to search the directory tree
# e.g. active directory: SASLAUTHD_LDAP_BIND_DN=cn=Administrator,cn=Users,dc=mydomain,dc=net
# e.g. openldap: SASLAUTHD_LDAP_BIND_DN=cn=admin,dc=mydomain,dc=net
SASLAUTHD_LDAP_BIND_DN=
# empty => Use value of LDAP_BIND_PW
SASLAUTHD_LDAP_PASSWORD=
# empty => Use value of LDAP_SEARCH_BASE
# specify the search base
SASLAUTHD_LDAP_SEARCH_BASE=
# empty => default filter `(&(uniqueIdentifier=%u)(mailEnabled=TRUE))`
# e.g. for active directory: `(&(sAMAccountName=%U)(objectClass=person))`
# e.g. for openldap: `(&(uid=%U)(objectClass=person))`
SASLAUTHD_LDAP_FILTER=
# empty => no
# yes => LDAP over TLS enabled for SASL
# If set to yes, the protocol in SASLAUTHD_LDAP_SERVER must be ldap:// or missing.
SASLAUTHD_LDAP_START_TLS=
# empty => no
# yes => Require and verify server certificate
# If yes you must/could specify SASLAUTHD_LDAP_TLS_CACERT_FILE or SASLAUTHD_LDAP_TLS_CACERT_DIR.
SASLAUTHD_LDAP_TLS_CHECK_PEER=
# File containing CA (Certificate Authority) certificate(s).
# empty => Nothing is added to the configuration
# Any value => Fills the `ldap_tls_cacert_file` option
SASLAUTHD_LDAP_TLS_CACERT_FILE=
# Path to directory with CA (Certificate Authority) certificates.
# empty => Nothing is added to the configuration
# Any value => Fills the `ldap_tls_cacert_dir` option
SASLAUTHD_LDAP_TLS_CACERT_DIR=
# Specify what password attribute to use for password verification.
# empty => Nothing is added to the configuration but the documentation says it is `userPassword` by default.
# Any value => Fills the `ldap_password_attr` option
SASLAUTHD_LDAP_PASSWORD_ATTR=
# empty => `bind` will be used as a default value
# `fastbind` => The fastbind method is used
# `custom` => The custom method uses userPassword attribute to verify the password
SASLAUTHD_LDAP_AUTH_METHOD=
# Specify the authentication mechanism for SASL bind
# empty => Nothing is added to the configuration
# Any value => Fills the `ldap_mech` option
SASLAUTHD_LDAP_MECH=
# -----------------------------------------------
# --- SRS Section -------------------------------
# -----------------------------------------------
# envelope_sender => Rewrite only envelope sender address (default)
# header_sender => Rewrite only header sender (not recommended)
# envelope_sender,header_sender => Rewrite both senders
# An email has an "envelope" sender (indicating the sending server) and a
# "header" sender (indicating who sent it). More strict SPF policies may require
# you to replace both instead of just the envelope sender.
SRS_SENDER_CLASSES=envelope_sender
# empty => Envelope sender will be rewritten for all domains
# provide comma separated list of domains to exclude from rewriting
SRS_EXCLUDE_DOMAINS=
# empty => generated when the image is built
# provide a secret to use in base64
# you may specify multiple keys, comma separated. the first one is used for
# signing and the remaining will be used for verification. this is how you
# rotate and expire keys
SRS_SECRET=
# -----------------------------------------------
# --- Default Relay Host Section ----------------
# -----------------------------------------------
# Setup relaying all mail through a default relay host
#
# empty => don't configure default relay host
# default host and optional port to relay all mail through
DEFAULT_RELAY_HOST=
# -----------------------------------------------
# --- Multi-Domain Relay Section ----------------
# -----------------------------------------------
# Setup relaying for multiple domains based on the domain name of the sender
# optionally uses usernames and passwords in postfix-sasl-password.cf and relay host mappings in postfix-relaymap.cf
#
# empty => don't configure relay host
# default host to relay mail through
RELAY_HOST=
# empty => 25
# default port to relay mail
RELAY_PORT=25
# empty => no default
# default relay username (if no specific entry exists in postfix-sasl-password.cf)
RELAY_USER=
# empty => no default
# password for default relay user
RELAY_PASSWORD=本文由 老孙博客 原创发布
转载请注明出处