️ feat: 1. Run the web statically 2. Final running the image using alpine, the image size is from 500M -> 170M 3. Upgrade tdlib

This commit is contained in:
jarvis2f 2025-02-26 21:41:18 +08:00
parent c4714170e2
commit 6777977d70
17 changed files with 68019 additions and 5893 deletions

4
.gitignore vendored
View file

@ -6,8 +6,8 @@ out/
*/.gradle
**/*build*/
app/
app-test/
/app/
/app-test/
**/*.log
api.log.lck

View file

@ -12,13 +12,12 @@ RUN gradle shadowJar --no-daemon && \
cp /app/build/libs/*.jar /app/api.jar && \
jdeps --print-module-deps --ignore-missing-deps /app/api.jar > /app/dependencies.txt
FROM openjdk:21-jdk-slim AS runtime-builder
FROM eclipse-temurin:21-jdk-alpine AS runtime-builder
WORKDIR /custom-jre
COPY --from=api-builder /app/dependencies.txt .
RUN apt-get update && \
apt-get install -y --no-install-recommends binutils && \
RUN apk add --no-cache binutils && \
jlink \
--add-modules $(cat dependencies.txt) \
--output jre \
@ -26,9 +25,7 @@ RUN apt-get update && \
--no-man-pages \
--no-header-files \
--compress=2 && \
apt-get purge -y binutils && \
apt-get autoremove -y && \
rm -rf /var/lib/apt/lists/*
apk del binutils
FROM node:21-alpine AS web-builder
@ -45,54 +42,34 @@ RUN npm ci --frozen-lockfile
COPY ./web .
RUN npm run build
FROM node:21-slim AS final
FROM alpine:3.14.10 AS final
WORKDIR /app
ARG LIB_PATH=/app/tdlib
ARG TARGETARCH
ENV JAVA_HOME=/jre \
PATH="/jre/bin:$PATH" \
LANG=C.UTF-8 \
NGINX_PORT=80
RUN npm install -g pm2 && \
apt-get update && \
apt-get install -y --no-install-recommends nginx wget curl unzip tini gosu gettext && \
mkdir -p $LIB_PATH && \
wget --no-check-certificate -q -O libs.zip https://github.com/p-vorobyev/spring-boot-starter-telegram/releases/download/1.15.0/libs.zip && \
unzip -q libs.zip -d /tmp/tdlib && \
if [ "$(uname -m)" = "x86_64" ]; then \
mv /tmp/tdlib/libs/linux_x64/* $LIB_PATH && \
wget http://nz2.archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2.24_amd64.deb && \
dpkg -i libssl1.1_1.1.1f-1ubuntu2.24_amd64.deb && \
rm libssl1.1_1.1.1f-1ubuntu2.24_amd64.deb; \
elif [ "$(uname -m)" = "aarch64" ]; then \
mv /tmp/tdlib/libs/linux_arm64/* $LIB_PATH && \
wget http://ports.ubuntu.com/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2_arm64.deb && \
dpkg -i libssl1.1_1.1.1f-1ubuntu2_arm64.deb && \
rm libssl1.1_1.1.1f-1ubuntu2_arm64.deb; \
else \
echo "Unsupported architecture: $(uname -m)" && \
exit 1; \
fi && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* ./libs.zip && \
RUN addgroup -S tf && \
adduser -S -G tf tf && \
apk add --no-cache nginx wget curl unzip tini su-exec gettext openssl libstdc++ gcompat libc6-compat && \
rm -rf /tmp/* /var/tmp/* && \
touch /run/nginx.pid && \
chown -R 1000:1000 /app /etc/nginx /var/lib/nginx /var/log/nginx /run/nginx.pid && \
chown -R tf:tf /app /etc/nginx /var/lib/nginx /var/log/nginx /run/nginx.pid && \
echo '#!/bin/sh\njava -Djava.library.path=/app/tdlib -cp /app/api.jar telegram.files.Maintain "$@"' > /usr/bin/tfm && \
chmod +x /usr/bin/tfm
COPY --from=runtime-builder --chown=1000:1000 /custom-jre/jre /jre
COPY --from=api-builder --chown=1000:1000 /app/api.jar /app/api.jar
COPY --from=web-builder --chown=1000:1000 /web/public /app/web/public
COPY --from=web-builder --chown=1000:1000 /web/.next/standalone /app/web/
COPY --from=web-builder --chown=1000:1000 /web/.next/static /app/web/.next/static
COPY --from=runtime-builder --chown=tf:tf /custom-jre/jre /jre
COPY --from=api-builder --chown=tf:tf /app/api.jar /app/api.jar
COPY --from=web-builder --chown=tf:tf /web/out /app/web/
COPY --chown=1000:1000 ./web/pm2.json /app/web/
COPY --chown=1000:1000 ./entrypoint.sh .
COPY --chown=1000:1000 ./nginx.conf.template /etc/nginx/nginx.conf.template
COPY --chown=tf:tf ./tdlib/linux_$TARGETARCH /app/tdlib
COPY --chown=tf:tf ./entrypoint.sh .
COPY --chown=tf:tf ./nginx.conf.template /etc/nginx/nginx.conf.template
EXPOSE 80
EXPOSE $NGINX_PORT
ENTRYPOINT ["/usr/bin/tini", "--"]
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["/bin/sh", "./entrypoint.sh"]

View file

@ -1,5 +1,5 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2025
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@ -157,7 +157,7 @@ public final class Client {
* @param maxVerbosityLevel The maximum verbosity level of messages for which the callback will be called.
* @param logMessageHandler Handler for messages that are added to the internal TDLib log. Pass null to remove the handler.
*/
public static void setLogMessageHandler(int maxVerbosityLevel, LogMessageHandler logMessageHandler) {
public static void setLogMessageHandler(int maxVerbosityLevel, Client.LogMessageHandler logMessageHandler) {
nativeClientSetLogMessageHandler(maxVerbosityLevel, logMessageHandler);
}

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,6 @@ PGID=${PGID:-0}
# Store PIDs in variables
JAVA_PID=""
PM2_PID=""
NGINX_PID=""
cleanup() {
@ -18,9 +17,6 @@ cleanup() {
if [ -n "$JAVA_PID" ]; then
kill -TERM "$JAVA_PID" 2>/dev/null || true
fi
if [ -n "$PM2_PID" ]; then
kill -TERM "$PM2_PID" 2>/dev/null || true
fi
if [ -n "$NGINX_PID" ]; then
kill -TERM "$NGINX_PID" 2>/dev/null || true
fi
@ -33,15 +29,14 @@ cleanup() {
setup_permissions() {
if [ "$(id -u)" = "0" ] && [ "$PUID" != "0" ]; then
echo "Setting up directory permissions..."
mkdir -p /.pm2
chown -R "${PUID}:${PGID}" /app /etc/nginx /var/lib/nginx /var/log/nginx /run/nginx.pid /etc/nginx/nginx.conf /.pm2
chown -R "${PUID}:${PGID}" /app /etc/nginx /var/lib/nginx /var/log/nginx /run/nginx.pid /etc/nginx/nginx.conf
fi
}
start_services() {
cmd_prefix=""
if [ "$(id -u)" = "0" ] && [ "$PUID" != "0" ]; then
cmd_prefix="gosu ${PUID}:${PGID}"
cmd_prefix="su-exec ${PUID}:${PGID}"
fi
echo "Starting Java service..."
@ -52,14 +47,6 @@ start_services() {
fi
JAVA_PID=$!
echo "Starting PM2 service..."
if [ -n "$cmd_prefix" ]; then
$cmd_prefix pm2 start /app/web/pm2.json --no-daemon --silent &
else
pm2 start /app/web/pm2.json --no-daemon --silent &
fi
PM2_PID=$!
echo "Starting Nginx service..."
if [ -n "$cmd_prefix" ]; then
$cmd_prefix nginx -g 'daemon off;' &

View file

@ -0,0 +1,37 @@
FROM alpine:3.14 AS builder
RUN echo "http://dl-cdn.alpinelinux.org/alpine/v3.14/community" >> /etc/apk/repositories && \
apk update && \
apk upgrade && \
apk add alpine-sdk linux-headers git zlib-dev openssl-dev gperf php cmake openjdk8 curl
WORKDIR /build
RUN git clone https://github.com/tdlib/td.git && \
cd td && \
mkdir -p build && \
cd build && \
cmake -DCMAKE_BUILD_TYPE=Release \
-DJAVA_HOME=/usr/lib/jvm/java-1.8-openjdk/ \
-DCMAKE_INSTALL_PREFIX:PATH=../example/java/td \
-DTD_ENABLE_JNI=ON .. && \
cmake --build . --target install && \
cd .. && \
cd example/java && \
mkdir -p build && \
cd build && \
cmake -DCMAKE_BUILD_TYPE=Release \
-DJAVA_HOME=/usr/lib/jvm/java-1.8-openjdk/ \
-DCMAKE_INSTALL_PREFIX:PATH=../../../tdlib \
-DTd_DIR:PATH=$(readlink -f ../td/lib/cmake/Td) .. && \
cmake --build . --target install
RUN mkdir -p /output && \
cp /build/td/tdlib/bin/libtdjni.so /output/ && \
cp -r /build/td/example/java /output/java
FROM alpine:3.14
WORKDIR /tdlib-output
COPY --from=builder /output /tdlib-output/libs
CMD ["cp", "-r", "/tdlib-output/libs", "/app/tdlib"]

27
misc/build-tdlib.sh Normal file
View file

@ -0,0 +1,27 @@
#!/bin/bash
ARCH=${1:-arm64}
DOCKERFILE="Dockerfile.tdlib-builder"
IMAGE_NAME="tdlib-builder"
if [ "$ARCH" == "arm64" ]; then
OUTPUT_DIR="./tdlib/linux_arm64"
elif [ "$ARCH" == "x86_64" ]; then
OUTPUT_DIR="./tdlib/linux_x86_64"
else
echo "Unsupported architecture: $ARCH"
echo "Usage: $0 [arm64|x86_64]"
exit 1
fi
mkdir -p "$OUTPUT_DIR"
echo "Building $ARCH TDLib builder Docker image..."
docker build -t "$IMAGE_NAME" -f "$DOCKERFILE" .
echo "Running TDLib builder for $ARCH and extracting libraries..."
docker run --rm -v "$(pwd)/$OUTPUT_DIR":/app/tdlib "$IMAGE_NAME"
echo "Build complete! $ARCH TDLib libraries are available in the $OUTPUT_DIR directory"
echo "Structure:"
echo "- $OUTPUT_DIR/libtdjni.so"

View file

@ -1,4 +1,5 @@
worker_processes 1;
user tf tf;
events {
worker_connections 1024;
@ -9,6 +10,11 @@ http {
server {
listen ${NGINX_PORT};
root /app/web;
index index.html;
include /etc/nginx/mime.types;
# 定义 WS 的代理规则
location /ws {
proxy_pass http://localhost:8080;
@ -29,12 +35,37 @@ http {
}
location / {
proxy_pass http://localhost:3000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
try_files $uri $uri.html $uri/ =404;
}
location /_next/static/ {
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable";
}
gzip on;
gzip_disable "msie6";
gzip_comp_level 6;
gzip_min_length 256;
gzip_proxied any;
gzip_vary on;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types
text/plain
text/css
text/javascript
application/javascript
application/json
application/x-javascript
application/xml
application/xml+rss
application/vnd.ms-fontobject
application/x-font-ttf
font/opentype
image/svg+xml
image/x-icon;
# 日志配置
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;

BIN
tdlib/linux_amd64/libtdjni.so Executable file

Binary file not shown.

BIN
tdlib/linux_arm64/libtdjni.so Executable file

Binary file not shown.

View file

@ -6,16 +6,7 @@ import "./src/env.js";
/** @type {import("next").NextConfig} */
const config = {
async redirects() {
return [
{
source: "/account",
destination: "/",
permanent: true,
},
];
},
output: "standalone",
output: "export",
};
export default config;

View file

@ -1,17 +0,0 @@
import { Header } from "@/components/header";
import Files from "@/components/files";
export default async function ChatFilesPage({
params,
}: {
params: Promise<{ accountId: string; chatId: string }>;
}) {
const { accountId, chatId } = await params;
return (
<div className="container mx-auto px-4 py-6">
<Header />
<Files accountId={accountId} chatId={chatId} />
</div>
);
}

View file

@ -1,11 +0,0 @@
import { Header } from "@/components/header";
import { EmptyState } from "@/components/empty-state";
export default function AccountPage() {
return (
<div className="container mx-auto px-4 py-6">
<Header />
<EmptyState hasAccounts={true} message="Select a chat to view files" />
</div>
);
}

View file

@ -0,0 +1,22 @@
"use client";
import { Header } from "@/components/header";
import { EmptyState } from "@/components/empty-state";
import { useSearchParams } from "next/navigation";
import Files from "@/components/files";
export default function AccountPage() {
const searchParams = useSearchParams();
const accountId = searchParams.get("id");
const chatId = searchParams.get("chatId");
return (
<div className="container mx-auto px-4 py-6">
<Header />
{accountId && chatId ? (
<Files accountId={accountId} chatId={chatId} />
) : (
<EmptyState hasAccounts={true} message="Select a chat to view files" />
)}
</div>
);
}

View file

@ -1,6 +1,6 @@
"use client";
import { useParams, useRouter } from "next/navigation";
import { useRouter, useSearchParams } from "next/navigation";
import { useToast } from "@/hooks/use-toast";
import { createContext, useContext, useEffect, useMemo, useState } from "react";
import type { TelegramAccount } from "@/lib/types";
@ -33,10 +33,10 @@ export const TelegramAccountProvider: React.FC<
isLoading,
isValidating,
} = useSWR<TelegramAccount[]>(`/telegrams`);
const params = useParams();
const router = useRouter();
const { toast } = useToast();
const routerAccountId = params.accountId as string;
const searchParams = useSearchParams();
const routerAccountId = searchParams.get("id") ?? undefined;
const [accountId, setAccountId] = useState<string | undefined>(
routerAccountId,
);
@ -76,7 +76,7 @@ export const TelegramAccountProvider: React.FC<
title: "Account Changed",
description: `Switched to ${accounts?.find((a) => a.id === newAccountId)?.name}'s account`,
});
router.push(`/account/${newAccountId}`);
router.push(`/accounts?id=${newAccountId}`);
}
};

View file

@ -1,5 +1,5 @@
"use client";
import { useParams, useRouter } from "next/navigation";
import { useRouter, useSearchParams } from "next/navigation";
import { useToast } from "@/hooks/use-toast";
import { createContext, useContext, useMemo, useState } from "react";
import { type TelegramChat } from "@/lib/types";
@ -33,10 +33,11 @@ export const TelegramChatProvider: React.FC<TelegramChatProviderProps> = ({
}) => {
const [query, setQuery] = useState("");
const [archived, setArchived] = useState(false);
const params = useParams<{ accountId: string; chatId: string }>();
const router = useRouter();
const { toast } = useToast();
const { accountId, chatId } = params;
const searchParams = useSearchParams();
const accountId = searchParams.get("id") ?? "";
const chatId = searchParams.get("chatId") ?? "";
const handleQueryChange = useDebouncedCallback((search: string) => {
setQuery(search);
@ -64,7 +65,7 @@ export const TelegramChatProvider: React.FC<TelegramChatProviderProps> = ({
toast({ title: "Error", description: "Failed to switch chat" });
return;
}
router.push(`/account/${accountId}/${newChatId}`);
router.push(`/accounts?id=${accountId}&chatId=${newChatId}`);
};
return (