Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ on:

env:
DOCKERHUB_PLATFORMS: linux/amd64,linux/arm64
SOLR_IMAGE: ${{ secrets.DOCKERHUB_USERNAME }}/lbbs-solr
APACHE_IMAGE: ${{ secrets.DOCKERHUB_USERNAME }}/lbbs-apache
PHP_IMAGE: ${{ secrets.DOCKERHUB_USERNAME }}/lbbs-php
BBSD_IMAGE: ${{ secrets.DOCKERHUB_USERNAME }}/lbbs-bbsd
Expand Down Expand Up @@ -50,6 +51,19 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build the Docker image (solr)
id: push-solr
uses: docker/build-push-action@v5
with:
platforms: ${{ env.DOCKERHUB_PLATFORMS }}
context: .
file: Dockerfile/dockerfile.solr.testing
push: true
tags: |
${{ env.SOLR_IMAGE }}:testing-${{ env.BUILD_DATE }}
${{ env.SOLR_IMAGE }}:testing
labels: ${{ steps.meta.outputs.labels }}

- name: Build the Docker image (apache)
id: push-apache
uses: docker/build-push-action@v5
Expand Down
4 changes: 3 additions & 1 deletion Dockerfile/dockerfile.apache.testing
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ COPY ./leafok_bbs/manage /var/www/html/manage
RUN mkdir -p /var/www/html/bbs/cache \
/var/www/html/bbs/upload \
/var/www/html/bbs/images/face/upload_photo \
/var/www/html/conf /var/www/html/stat
/var/www/html/conf /var/www/html/stat \
/var/www/html/export_xml

# Copy the composer binary from the installer stage into your final image
COPY --from=composer_installer /usr/local/composer/vendor /var/www/html/vendor
Expand All @@ -34,6 +35,7 @@ COPY ./conf/site.conf.php /var/www/html/conf/site.conf.php
COPY ./conf/smtp.conf.php /var/www/html/conf/smtp.conf.php
COPY ./leafok_bbs/TODO/conf/badwords.conf /var/www/html/conf/badwords.conf
COPY ./leafok_bbs/TODO/conf/deny_reg.conf /var/www/html/conf/deny_reg.conf
COPY ./conf/solr.conf.php /var/www/html/conf/solr.conf.php

COPY ./data/eula.txt /var/www/html/bbs/doc/eula.txt

Expand Down
8 changes: 8 additions & 0 deletions Dockerfile/dockerfile.php.testing
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ FROM php:8.4-fpm
# Install PHP extensions
RUN apt-get update && apt-get install -y \
libfreetype-dev libjpeg62-turbo-dev libpng-dev \
libcurl4-openssl-dev libxml2-dev \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) gd mysqli \
&& rm -rf /var/lib/apt/lists/*
Expand All @@ -14,3 +15,10 @@ COPY ./leafok_bbs/Dockerfile/php.ini /usr/local/etc/php/php.ini
# Set ownership to www-data user and group
RUN mkdir -p /var/lib/php/sessions \
&& chown -R www-data:www-data /var/lib/php/sessions

# Install the solr extension via PECL
RUN pecl install solr \
&& docker-php-ext-enable solr

COPY ./script/export_xml_to_solr.sh /usr/local/bin/export_xml_to_solr.sh
RUN chmod +x /usr/local/bin/export_xml_to_solr.sh
13 changes: 13 additions & 0 deletions Dockerfile/dockerfile.solr.testing
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM solr:9.10 AS base

# Start from the official Solr slim image
FROM solr:9.10-slim

# Copy the composer binary from the installer stage into your final image
COPY --from=base /opt/solr/modules/analysis-extras/lib/lucene-analysis-smartcn-*.jar /opt/solr/modules/analysis-extras/lib/

# Copy custom Solr configuration
COPY ./solr-config /opt/solr/server/solr/configsets/lbbs

# Set environment variable to enable config library
ENV SOLR_CONFIG_LIB_ENABLED="true"
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,14 @@ docker compose pull
docker compose up -d
```

### Update Solr data from database, on demand or periodically
```bash
docker exec -it lbbs-combo-php-1 /usr/local/bin/export_xml_to_solr.sh
```

## Copyright

Copyright (C) 2004-2026 Leaflet <leaflet@leafok.com>
Copyright (C) 2001-2026 Leaflet <leaflet@leafok.com>

## License

Expand Down
9 changes: 7 additions & 2 deletions README.zh_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,15 @@ docker compose pull
docker compose up -d
```

### 根据需要或定期从数据库更新 Solr 数据
```bash
docker exec -it lbbs-combo-php-1 /usr/local/bin/export_xml_to_solr.sh
```

## 版权

版权所有 (C) 2004-2026 Leaflet <leaflet@leafok.com>
版权所有 (C) 2001-2026 Leaflet <leaflet@leafok.com>

## 许可证

本程序是自由软件;您可以依据自由软件基金会发布的 [GNU 通用公共许可证](LICENSE) 条款重新发布和/或修改本程序;许可证版本为第 3 版,或者(您可选)任何更高版本。
本程序是自由软件;您可以依据自由软件基金会发布的 [GNU 通用公共许可证](LICENSE) 条款重新发布和/或修改本程序;许可证版本为第 3 版,或者(您可选)任何更高版本。
2 changes: 2 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ fi

docker buildx inspect multi-platform-builder --bootstrap

docker buildx build . --platform=$DOCKER_PLATFORMS --file Dockerfile/dockerfile.solr.testing --tag leafok/lbbs-solr:testing
docker buildx build . --platform=$DOCKER_PLATFORMS --file Dockerfile/dockerfile.apache.testing --tag leafok/lbbs-apache:testing
docker buildx build . --platform=$DOCKER_PLATFORMS --file Dockerfile/dockerfile.php.testing --tag leafok/lbbs-php:testing
docker buildx build . --platform=$DOCKER_PLATFORMS --file Dockerfile/dockerfile.bbsd.testing --tag leafok/lbbs-bbsd:testing

docker buildx build . --platform=$RUN_PLATFORM --file Dockerfile/dockerfile.solr.testing --tag leafok/lbbs-solr:testing --load
docker buildx build . --platform=$RUN_PLATFORM --file Dockerfile/dockerfile.apache.testing --tag leafok/lbbs-apache:testing --load
docker buildx build . --platform=$RUN_PLATFORM --file Dockerfile/dockerfile.php.testing --tag leafok/lbbs-php:testing --load
docker buildx build . --platform=$RUN_PLATFORM --file Dockerfile/dockerfile.bbsd.testing --tag leafok/lbbs-bbsd:testing --load
10 changes: 10 additions & 0 deletions conf/solr.conf.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php
$solr_options = array
(
"hostname" => "solr",
"port" => "8983",
// "login" => "username",
// "password" => "password",
// "auth" => "HTTPBasicAuth",
"path" => "/solr/lbbs",
);
22 changes: 22 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,24 @@ services:
- --innodb-buffer-pool-size=64M
- --max-connections=15

solr:
image: leafok/lbbs-solr:testing
platform: ${RUN_PLATFORM}
restart: unless-stopped
build:
context: .
dockerfile: Dockerfile/dockerfile.solr.testing
volumes:
- solr-data:/var/solr
environment:
SOLR_JAVA_MEM: "-Xms256m -Xmx512m"
command:
- solr-precreate
- lbbs
- /opt/solr/server/solr/configsets/lbbs
networks:
- app-network

apache:
image: leafok/lbbs-apache:testing
platform: ${RUN_PLATFORM}
Expand All @@ -33,6 +51,7 @@ services:
- apache-logs:/usr/local/apache2/logs
depends_on:
- php
- solr
networks:
- app-network

Expand Down Expand Up @@ -107,6 +126,9 @@ volumes:
mysql-data:
# Volume for persistent storage of mysql data
driver: local
solr-data:
# Volume for persistent storage of solr data
driver: local
www-data:
# Volume for persistent storage of web data
driver: local
Expand Down
2 changes: 1 addition & 1 deletion lbbs
17 changes: 17 additions & 0 deletions script/export_xml_to_solr.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/bash

php /var/www/html/manage/export_article.php 2>/dev/null |
while read file; do
echo $file
curl http://solr:8983/solr/lbbs/update -X POST -H 'Content-type:text/xml' \
--data-binary @/var/www/html/export_xml/$file.xml 2>/dev/null
if [ $? -ne 0 ]; then
echo "Solr update failed!"
exit 2
fi
done

if [ $? -ne 0 ]; then
echo "Export XML data failed!"
exit 1
fi
180 changes: 180 additions & 0 deletions solr-config/conf/managed-schema.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Solr managed schema - automatically generated - DO NOT EDIT -->
<schema name="default-config" version="1.7">
<uniqueKey>id</uniqueKey>
<fieldType name="_nest_path_" class="solr.NestPathField" maxCharsForDocValues="-1" omitNorms="true" omitTermFreqAndPositions="true" stored="false" multiValued="false"/>
<fieldType name="ancestor_path" class="solr.TextField">
<analyzer type="index">
<tokenizer name="keyword"/>
</analyzer>
<analyzer type="query">
<tokenizer delimiter="/" name="pathHierarchy"/>
</analyzer>
</fieldType>
<fieldType name="binary" class="solr.BinaryField"/>
<fieldType name="boolean" class="solr.BoolField" sortMissingLast="true"/>
<fieldType name="booleans" class="solr.BoolField" sortMissingLast="true" multiValued="true"/>
<fieldType name="delimited_payloads_float" class="solr.TextField" indexed="true" stored="false">
<analyzer>
<tokenizer name="whitespace"/>
<filter name="delimitedPayload" encoder="float"/>
</analyzer>
</fieldType>
<fieldType name="delimited_payloads_int" class="solr.TextField" indexed="true" stored="false">
<analyzer>
<tokenizer name="whitespace"/>
<filter name="delimitedPayload" encoder="integer"/>
</analyzer>
</fieldType>
<fieldType name="delimited_payloads_string" class="solr.TextField" indexed="true" stored="false">
<analyzer>
<tokenizer name="whitespace"/>
<filter name="delimitedPayload" encoder="identity"/>
</analyzer>
</fieldType>
<fieldType name="descendent_path" class="solr.TextField">
<analyzer type="index">
<tokenizer delimiter="/" name="pathHierarchy"/>
</analyzer>
<analyzer type="query">
<tokenizer name="keyword"/>
</analyzer>
</fieldType>
<fieldType name="ignored" class="solr.StrField" indexed="false" stored="false" docValues="false" multiValued="true"/>
<fieldType name="location" class="solr.LatLonPointSpatialField"/>
<fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType" geo="true" maxDistErr="0.001" distErrPct="0.025" distanceUnits="kilometers"/>
<fieldType name="lowercase" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer name="keyword"/>
<filter name="lowercase"/>
</analyzer>
</fieldType>
<fieldType name="pdate" class="solr.DatePointField"/>
<fieldType name="pdates" class="solr.DatePointField" multiValued="true"/>
<fieldType name="pdouble" class="solr.DoublePointField"/>
<fieldType name="pdoubles" class="solr.DoublePointField" multiValued="true"/>
<fieldType name="pfloat" class="solr.FloatPointField"/>
<fieldType name="pfloats" class="solr.FloatPointField" multiValued="true"/>
<fieldType name="phonetic_en" class="solr.TextField" indexed="true" stored="false">
<analyzer>
<tokenizer name="standard"/>
<filter inject="false" name="doubleMetaphone"/>
</analyzer>
</fieldType>
<fieldType name="pint" class="solr.IntPointField"/>
<fieldType name="pints" class="solr.IntPointField" multiValued="true"/>
<fieldType name="plong" class="solr.LongPointField"/>
<fieldType name="plongs" class="solr.LongPointField" multiValued="true"/>
<fieldType name="point" class="solr.PointType" dimension="2" subFieldSuffix="_d"/>
<fieldType name="random" class="solr.RandomSortField" indexed="true"/>
<fieldType name="rank" class="solr.RankField"/>
<fieldType name="string" class="solr.StrField" sortMissingLast="true"/>
<fieldType name="strings" class="solr.StrField" sortMissingLast="true" multiValued="true"/>
<fieldType name="text_gen_sort" class="solr.SortableTextField" positionIncrementGap="100" multiValued="true">
<analyzer type="index">
<tokenizer name="standard"/>
<filter name="stop" ignoreCase="true" words="stopwords.txt"/>
<filter name="lowercase"/>
</analyzer>
<analyzer type="query">
<tokenizer name="standard"/>
<filter name="stop" ignoreCase="true" words="stopwords.txt"/>
<filter name="synonymGraph" expand="true" ignoreCase="true" synonyms="synonyms.txt"/>
<filter name="lowercase"/>
</analyzer>
</fieldType>
<fieldType name="text_general" class="solr.TextField" positionIncrementGap="100" multiValued="true">
<analyzer type="index">
<tokenizer name="standard"/>
<filter name="stop" ignoreCase="true" words="stopwords.txt"/>
<filter name="lowercase"/>
</analyzer>
<analyzer type="query">
<tokenizer name="standard"/>
<filter name="stop" ignoreCase="true" words="stopwords.txt"/>
<filter name="synonymGraph" expand="true" ignoreCase="true" synonyms="synonyms.txt"/>
<filter name="lowercase"/>
</analyzer>
</fieldType>
<fieldType name="text_general_rev" class="solr.TextField" positionIncrementGap="100">
<analyzer type="index">
<tokenizer name="standard"/>
<filter name="stop" ignoreCase="true" words="stopwords.txt"/>
<filter name="lowercase"/>
<filter withOriginal="true" maxPosAsterisk="3" maxFractionAsterisk="0.33" name="reversedWildcard" maxPosQuestion="2"/>
</analyzer>
<analyzer type="query">
<tokenizer name="standard"/>
<filter name="synonymGraph" expand="true" ignoreCase="true" synonyms="synonyms.txt"/>
<filter name="stop" ignoreCase="true" words="stopwords.txt"/>
<filter name="lowercase"/>
</analyzer>
</fieldType>
<fieldType name="text_zh" class="solr.TextField" positionIncrementGap="100">
<analyzer type="index">
<tokenizer class="org.apache.lucene.analysis.cn.smart.HMMChineseTokenizerFactory" />
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
<analyzer type="query">
<tokenizer class="org.apache.lucene.analysis.cn.smart.HMMChineseTokenizerFactory" />
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
</fieldType>
<field name="ArticleId" type="pint" indexed="true" stored="true"/>
<field name="ArticleTitle" type="text_zh" indexed="true" stored="true"/>
<field name="Content" type="text_zh" indexed="true" stored="true"/>
<field name="Credit" type="pint" indexed="true" stored="true"/>
<field name="Excerption" type="pint" indexed="true" stored="true"/>
<field name="ExpressionIcon" type="pint" indexed="true" stored="true"/>
<field name="Length" type="plong" indexed="true" stored="true"/>
<field name="Photo" type="text_general" indexed="true" stored="true"/>
<field name="PostDateTime" type="pdate" indexed="true" stored="true"/>
<field name="PostIP" type="text_general" indexed="true" stored="true"/>
<field name="PostUserId" type="pint" indexed="true" stored="true"/>
<field name="PostUserName" type="text_general" indexed="true" stored="true"/>
<field name="PostUserNickName" type="text_zh" indexed="true" stored="true"/>
<field name="SectionId" type="pint" indexed="true" stored="true"/>
<field name="TopicId" type="pint" indexed="true" stored="true"/>
<field name="Transship" type="pint" indexed="true" stored="true"/>
<field name="_nest_path_" type="_nest_path_"/>
<field name="_root_" type="string" indexed="true" stored="false"/>
<field name="_text_" type="text_general" multiValued="true" indexed="true" stored="false"/>
<field name="_version_" type="plong" indexed="false" stored="false"/>
<field name="delete" type="text_general"/>
<field name="id" type="string" multiValued="false" indexed="true" required="true" stored="true"/>
<dynamicField name="*_descendent_path" type="descendent_path" indexed="true" stored="true"/>
<dynamicField name="*_ancestor_path" type="ancestor_path" indexed="true" stored="true"/>
<dynamicField name="*_txt_sort" type="text_gen_sort" indexed="true" stored="true"/>
<dynamicField name="ignored_*" type="ignored"/>
<dynamicField name="*_txt_rev" type="text_general_rev" indexed="true" stored="true"/>
<dynamicField name="*_phon_en" type="phonetic_en" indexed="true" stored="true"/>
<dynamicField name="*_s_lower" type="lowercase" indexed="true" stored="true"/>
<dynamicField name="random_*" type="random"/>
<dynamicField name="*_t_sort" type="text_gen_sort" multiValued="false" indexed="true" stored="true"/>
<dynamicField name="*_txt_zh" type="text_zh" omitNorms="false" multiValued="true" indexed="true" stored="true"/>
<dynamicField name="*_point" type="point" indexed="true" stored="true"/>
<dynamicField name="*_srpt" type="location_rpt" indexed="true" stored="true"/>
<dynamicField name="attr_*" type="text_general" multiValued="true" indexed="true" stored="true"/>
<dynamicField name="*_dts" type="pdates" indexed="true" stored="true"/>
<dynamicField name="*_txt" type="text_general" indexed="true" stored="true"/>
<dynamicField name="*_str" type="strings" docValues="true" indexed="false" stored="false" useDocValuesAsStored="false"/>
<dynamicField name="*_dpf" type="delimited_payloads_float" indexed="true" stored="true"/>
<dynamicField name="*_dpi" type="delimited_payloads_int" indexed="true" stored="true"/>
<dynamicField name="*_dps" type="delimited_payloads_string" indexed="true" stored="true"/>
<dynamicField name="*_is" type="pints" indexed="true" stored="true"/>
<dynamicField name="*_ss" type="strings" indexed="true" stored="true"/>
<dynamicField name="*_ls" type="plongs" indexed="true" stored="true"/>
<dynamicField name="*_bs" type="booleans" indexed="true" stored="true"/>
<dynamicField name="*_fs" type="pfloats" indexed="true" stored="true"/>
<dynamicField name="*_ds" type="pdoubles" indexed="true" stored="true"/>
<dynamicField name="*_dt" type="pdate" indexed="true" stored="true"/>
<dynamicField name="*_i" type="pint" indexed="true" stored="true"/>
<dynamicField name="*_s" type="string" indexed="true" stored="true"/>
<dynamicField name="*_l" type="plong" indexed="true" stored="true"/>
<dynamicField name="*_b" type="boolean" indexed="true" stored="true"/>
<dynamicField name="*_f" type="pfloat" indexed="true" stored="true"/>
<dynamicField name="*_d" type="pdouble" indexed="true" stored="true"/>
<dynamicField name="*_t" type="text_general" multiValued="false" indexed="true" stored="true"/>
<dynamicField name="*_p" type="location" indexed="true" stored="true"/>
<copyField source="delete" dest="delete_str" maxChars="256"/>
</schema>
Loading