⚡️ 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:
parent
c4714170e2
commit
6777977d70
17 changed files with 68019 additions and 5893 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -6,8 +6,8 @@ out/
|
|||
*/.gradle
|
||||
**/*build*/
|
||||
|
||||
app/
|
||||
app-test/
|
||||
/app/
|
||||
/app-test/
|
||||
|
||||
**/*.log
|
||||
api.log.lck
|
||||
|
|
|
|||
59
Dockerfile
59
Dockerfile
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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;' &
|
||||
|
|
|
|||
37
misc/Dockerfile.tdlib-builder
Normal file
37
misc/Dockerfile.tdlib-builder
Normal 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
27
misc/build-tdlib.sh
Normal 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"
|
||||
|
|
@ -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
BIN
tdlib/linux_amd64/libtdjni.so
Executable file
Binary file not shown.
BIN
tdlib/linux_arm64/libtdjni.so
Executable file
BIN
tdlib/linux_arm64/libtdjni.so
Executable file
Binary file not shown.
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
22
web/src/app/accounts/page.tsx
Normal file
22
web/src/app/accounts/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
Loading…
Reference in a new issue