mirror of
https://github.com/bbernhard/signal-cli-rest-api.git
synced 2026-09-15 05:22:55 +00:00
Merge branch 'rootless_s6'
This commit is contained in:
commit
fd9bdae583
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -82,5 +82,5 @@ jobs:
|
||||
docker run --privileged --rm tonistiigi/binfmt --install all
|
||||
podman manifest create build
|
||||
podman build --format docker --platform linux/amd64,linux/arm64,linux/arm/v7 --manifest localhost/build .
|
||||
podman manifest push localhost/build docker://docker.io/bbernhard/signal-cli-rest-api:${EPOCHSECONDS}-ci
|
||||
podman manifest push localhost/build docker://docker.io/bbernhard/signal-cli-rest-api:${EPOCHSECONDS}-rootless-ci
|
||||
|
||||
|
||||
4
.github/workflows/release-dev-version.yml
vendored
4
.github/workflows/release-dev-version.yml
vendored
@ -85,5 +85,5 @@ jobs:
|
||||
docker run --privileged --rm tonistiigi/binfmt --install all
|
||||
podman manifest create build
|
||||
podman build --format docker --build-arg BUILD_VERSION_ARG=${VERSION} --manifest localhost/build --platform linux/amd64,linux/arm64,linux/arm/v7 .
|
||||
podman manifest push localhost/build docker://docker.io/bbernhard/signal-cli-rest-api:${VERSION}-dev
|
||||
podman manifest push localhost/build docker://docker.io/bbernhard/signal-cli-rest-api:latest-dev
|
||||
podman manifest push localhost/build docker://docker.io/bbernhard/signal-cli-rest-api:${VERSION}-rootless-dev
|
||||
podman manifest push localhost/build docker://docker.io/bbernhard/signal-cli-rest-api:latest-rootless-dev
|
||||
|
||||
@ -85,5 +85,5 @@ jobs:
|
||||
docker run --privileged --rm tonistiigi/binfmt --install all
|
||||
podman manifest create build
|
||||
podman build --format docker --build-arg BUILD_VERSION_ARG=${VERSION} --manifest localhost/build --platform linux/amd64,linux/arm64,linux/arm/v7 .
|
||||
podman manifest push localhost/build docker://docker.io/bbernhard/signal-cli-rest-api:${VERSION}
|
||||
podman manifest push localhost/build docker://docker.io/bbernhard/signal-cli-rest-api:latest
|
||||
podman manifest push localhost/build docker://docker.io/bbernhard/signal-cli-rest-api:${VERSION}-rootless
|
||||
podman manifest push localhost/build docker://docker.io/bbernhard/signal-cli-rest-api:rootless-latest
|
||||
|
||||
74
Dockerfile
74
Dockerfile
@ -3,6 +3,8 @@ ARG LIBSIGNAL_CLIENT_VERSION=0.99.1
|
||||
|
||||
ARG SWAG_VERSION=1.16.4
|
||||
|
||||
ARG S6_OVERLAY_VERSION=v3.2.2.0
|
||||
|
||||
ARG BUILD_VERSION_ARG=unset
|
||||
|
||||
FROM golang:1.26-trixie AS buildcontainer
|
||||
@ -38,7 +40,7 @@ RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
|
||||
|
||||
ENV JAVA_OPTS="-Djdk.lang.Process.launchMechanism=vfork"
|
||||
|
||||
ENV LANG en_US.UTF-8
|
||||
ENV LANG=en_US.UTF-8
|
||||
|
||||
RUN go install github.com/swaggo/swag/cmd/swag@v${SWAG_VERSION}
|
||||
|
||||
@ -120,43 +122,63 @@ RUN cd /tmp/signal-cli-rest-api-src/scripts && go build -o jsonrpc2-helper
|
||||
RUN cd /tmp/signal-cli-rest-api-src && go build -buildmode=plugin -o signal-cli-rest-api_plugin_loader.so plugin_loader.go
|
||||
|
||||
# Start a fresh container for release container
|
||||
FROM debian:trixie-slim
|
||||
|
||||
# eclipse-temurin doesn't provide a OpenJDK 21 image for armv7 (see https://github.com/adoptium/containers/issues/502). Until this
|
||||
# is fixed we use the standard ubuntu image
|
||||
#FROM eclipse-temurin:21-jre-jammy
|
||||
|
||||
FROM ubuntu:noble
|
||||
|
||||
ENV GIN_MODE=release
|
||||
|
||||
ENV PORT=8080
|
||||
|
||||
ARG TARGETARCH # set by buildx
|
||||
ARG SIGNAL_CLI_VERSION
|
||||
ARG BUILD_VERSION_ARG
|
||||
ARG S6_OVERLAY_VERSION
|
||||
ENV GIN_MODE=release
|
||||
|
||||
# Set environment variables to keep the image clean
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
ENV PORT=8080
|
||||
|
||||
ENV BUILD_VERSION=$BUILD_VERSION_ARG
|
||||
ENV SIGNAL_CLI_REST_API_PLUGIN_SHARED_OBJ_DIR=/usr/bin/
|
||||
|
||||
RUN dpkg-reconfigure debconf --frontend=noninteractive \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y --no-install-recommends util-linux supervisor openjdk-25-jre curl locales \
|
||||
&& apt-get install -y --no-install-recommends util-linux openjdk-25-jre curl locales xz-utils \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN if [ -z "$TARGETARCH" ]; then \
|
||||
# Fallback for older Docker versions not using BuildKit
|
||||
TARGETARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/'); \
|
||||
else \
|
||||
echo "Building for architecture: $TARGETARCH"; \
|
||||
fi;
|
||||
|
||||
# install s6-overlay as service control system
|
||||
RUN curl -fL -o /tmp/s6-overlay-noarch.tar.xz \
|
||||
"https://github.com/just-containers/s6-overlay/releases/download/${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz" && \
|
||||
tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz && \
|
||||
if [ "$TARGETARCH" = "amd64" ]; then S6_ARCH="x86_64"; \
|
||||
elif [ "$TARGETARCH" = "arm64" ]; then S6_ARCH="aarch64"; \
|
||||
elif [ "$TARGETARCH" = "arm" ]; then S6_ARCH="arm"; \
|
||||
else S6_ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/'); fi;\
|
||||
curl -fL -o /tmp/s6-overlay-bin.tar.xz \
|
||||
"https://github.com/just-containers/s6-overlay/releases/download/${S6_OVERLAY_VERSION}/s6-overlay-${S6_ARCH}.tar.xz" && \
|
||||
tar -C / -Jxpf /tmp/s6-overlay-bin.tar.xz && \
|
||||
rm /tmp/s6-overlay-*.tar.xz
|
||||
|
||||
COPY --from=buildcontainer /tmp/signal-cli-rest-api-src/signal-cli-rest-api /usr/bin/signal-cli-rest-api
|
||||
COPY --from=buildcontainer /opt/signal-cli-${SIGNAL_CLI_VERSION} /opt/signal-cli-${SIGNAL_CLI_VERSION}
|
||||
COPY --from=buildcontainer /tmp/signal-cli-native /opt/signal-cli-${SIGNAL_CLI_VERSION}/bin/signal-cli-native
|
||||
COPY --from=buildcontainer /tmp/signal-cli-rest-api-src/scripts/jsonrpc2-helper /usr/bin/jsonrpc2-helper
|
||||
COPY --from=buildcontainer /tmp/signal-cli-rest-api-src/signal-cli-rest-api_plugin_loader.so /usr/bin/signal-cli-rest-api_plugin_loader.so
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
|
||||
|
||||
RUN userdel ubuntu -r \
|
||||
&& groupadd -g 1000 signal-api \
|
||||
RUN groupadd -g 1000 signal-api \
|
||||
&& useradd --no-log-init -M -d /home -s /bin/bash -u 1000 -g 1000 signal-api \
|
||||
&& ln -s /opt/signal-cli-${SIGNAL_CLI_VERSION}/bin/signal-cli /usr/bin/signal-cli \
|
||||
&& ln -s /opt/signal-cli-${SIGNAL_CLI_VERSION}/bin/signal-cli-native /usr/bin/signal-cli-native \
|
||||
&& mkdir -p /signal-cli-config/ \
|
||||
&& mkdir -p /home/.local/share/signal-cli
|
||||
&& mkdir -p /home/.local/share/signal-cli \
|
||||
&& chown -R signal-api:signal-api /home
|
||||
|
||||
COPY --chmod=755 ./s6-services/ /etc/s6-overlay/s6-rc.d/
|
||||
|
||||
# remove the temporary created signal-cli-native on armv7, as GRAALVM doesn't support 32bit
|
||||
RUN arch="$(uname -m)"; \
|
||||
@ -168,16 +190,24 @@ RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
|
||||
dpkg-reconfigure --frontend=noninteractive locales && \
|
||||
update-locale LANG=en_US.UTF-8
|
||||
|
||||
ENV LANG en_US.UTF-8
|
||||
ENV LANG=en_US.UTF-8
|
||||
|
||||
EXPOSE ${PORT}
|
||||
|
||||
ENV SIGNAL_CLI_CONFIG_DIR=/home/.local/share/signal-cli
|
||||
ENV SIGNAL_CLI_UID=1000
|
||||
ENV SIGNAL_CLI_GID=1000
|
||||
ENV SIGNAL_CLI_CHOWN_ON_STARTUP=true
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
RUN mkdir -p /tmp/s6-runtime && chown -R signal-api:signal-api /tmp/s6-runtime /etc/s6-overlay
|
||||
|
||||
USER signal-api
|
||||
|
||||
# Mandatory ENV for non-root s6
|
||||
ENV S6_RUNTIME_PATH=/tmp/s6-runtime
|
||||
ENV S6_READ_ONLY_ROOT=1
|
||||
ENV S6_VERBOSITY=2
|
||||
|
||||
WORKDIR /home
|
||||
|
||||
ENTRYPOINT ["/init"]
|
||||
|
||||
HEALTHCHECK --interval=20s --timeout=10s --retries=3 \
|
||||
CMD curl -f http://localhost:${PORT}/v1/health || exit 1
|
||||
|
||||
@ -1,11 +1,16 @@
|
||||
services:
|
||||
signal-cli-rest-api:
|
||||
image: bbernhard/signal-cli-rest-api:latest
|
||||
user: "1000:1000" # change to UID:GID you preferre
|
||||
image: bbernhard/signal-cli-rest-api:rootless-latest
|
||||
environment:
|
||||
- MODE=normal #supported modes: json-rpc, native, normal
|
||||
#- AUTO_RECEIVE_SCHEDULE=0 22 * * * #enable this parameter on demand (see description below)
|
||||
ports:
|
||||
- "8080:8080" #map docker port 8080 to host port 8080.
|
||||
volumes:
|
||||
- "./signal-cli-config:/home/.local/share/signal-cli" #map "signal-cli-config" folder on host system into docker container. the folder contains the password and cryptographic keys when a new number is registered
|
||||
- "./signal-cli-config:/home/.local/share/signal-cli" #map "signal-cli-config" folder on host system into docker container. the folder contains the password and cryptographic keys when a new number is registered. Make sure that the user has permissions to read and write here.
|
||||
security_opt:
|
||||
- no-new-privileges:true # additonal security control
|
||||
tmpfs:
|
||||
- /run:exec,size=64m,uid=1000,gid=1000,mode=0755 # Make sure this UID:GID fits those from above
|
||||
|
||||
|
||||
43
entrypoint.old.sh
Executable file
43
entrypoint.old.sh
Executable file
@ -0,0 +1,43 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -x
|
||||
set -e
|
||||
|
||||
[ -z "${SIGNAL_CLI_CONFIG_DIR}" ] && echo "SIGNAL_CLI_CONFIG_DIR environmental variable needs to be set! Aborting!" && exit 1;
|
||||
|
||||
usermod -u ${SIGNAL_CLI_UID} signal-api
|
||||
groupmod -o -g ${SIGNAL_CLI_GID} signal-api
|
||||
|
||||
# Fix permissions to ensure backward compatibility if SIGNAL_CLI_CHOWN_ON_STARTUP is not set to "false"
|
||||
if [ "$SIGNAL_CLI_CHOWN_ON_STARTUP" != "false" ]; then
|
||||
echo "Changing ownership of ${SIGNAL_CLI_CONFIG_DIR} to ${SIGNAL_CLI_UID}:${SIGNAL_CLI_GID}"
|
||||
chown ${SIGNAL_CLI_UID}:${SIGNAL_CLI_GID} -R ${SIGNAL_CLI_CONFIG_DIR}
|
||||
else
|
||||
echo "Skipping chown on startup since SIGNAL_CLI_CHOWN_ON_STARTUP is set to 'false'"
|
||||
fi
|
||||
|
||||
# Show warning on docker exec
|
||||
cat <<EOF >> /root/.bashrc
|
||||
echo "WARNING: signal-cli-rest-api runs as signal-api (not as root!)"
|
||||
echo "Run 'su signal-api' before using signal-cli!"
|
||||
echo "If you want to use signal-cli directly, don't forget to specify the config directory. e.g: \"signal-cli --config ${SIGNAL_CLI_CONFIG_DIR}\""
|
||||
EOF
|
||||
|
||||
cap_prefix="-cap_"
|
||||
caps="$cap_prefix$(seq -s ",$cap_prefix" 0 $(cat /proc/sys/kernel/cap_last_cap))"
|
||||
|
||||
# TODO: check mode
|
||||
if [ "$MODE" = "json-rpc" ]
|
||||
then
|
||||
/usr/bin/jsonrpc2-helper
|
||||
if [ -n "$JAVA_OPTS" ] ; then
|
||||
echo "export JAVA_OPTS='$JAVA_OPTS'" >> /etc/default/supervisor
|
||||
fi
|
||||
service supervisor start
|
||||
supervisorctl start all
|
||||
fi
|
||||
|
||||
export HOST_IP=$(hostname -I | awk '{print $1}')
|
||||
|
||||
# Start API as signal-api user
|
||||
exec setpriv --reuid=${SIGNAL_CLI_UID} --regid=${SIGNAL_CLI_GID} --init-groups --inh-caps=$caps signal-cli-rest-api -signal-cli-config=${SIGNAL_CLI_CONFIG_DIR}
|
||||
3
s6-services/signal-api/run
Normal file
3
s6-services/signal-api/run
Normal file
@ -0,0 +1,3 @@
|
||||
#!/command/with-contenv sh
|
||||
# Use with-contenv to import environment variables like SIGNAL_CLI_CONFIG_DIR
|
||||
exec signal-cli-rest-api -signal-cli-config="${SIGNAL_CLI_CONFIG_DIR}"
|
||||
1
s6-services/signal-api/type
Normal file
1
s6-services/signal-api/type
Normal file
@ -0,0 +1 @@
|
||||
longrun
|
||||
10
s6-services/signal-json-rpc/run
Normal file
10
s6-services/signal-json-rpc/run
Normal file
@ -0,0 +1,10 @@
|
||||
#!/command/with-contenv sh
|
||||
# File: /etc/s6-overlay/s6-rc.d/signal-json-rpc/run
|
||||
|
||||
if [ "$MODE" != "json-rpc" ] && [ "$MODE" != "json-rpc-native" ]; then
|
||||
echo "Running as mode: $MODE - skipping json-rpc setup"
|
||||
sleep infinity # do nothing, but keep service running
|
||||
exit 0
|
||||
fi
|
||||
|
||||
exec jsonrpc2-helper
|
||||
1
s6-services/signal-json-rpc/type
Normal file
1
s6-services/signal-json-rpc/type
Normal file
@ -0,0 +1 @@
|
||||
longrun
|
||||
0
s6-services/user/contents.d/signal-api
Normal file
0
s6-services/user/contents.d/signal-api
Normal file
0
s6-services/user/contents.d/signal-json-rpc
Normal file
0
s6-services/user/contents.d/signal-json-rpc
Normal file
1
s6-services/user/type
Normal file
1
s6-services/user/type
Normal file
@ -0,0 +1 @@
|
||||
bundle
|
||||
@ -1,32 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os/exec"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/bbernhard/signal-cli-rest-api/utils"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const supervisorctlConfigTemplate = `
|
||||
[program:%s]
|
||||
process_name=%s
|
||||
command=%s --output=json --config %s%s daemon %s%s%s%s --tcp 127.0.0.1:%d
|
||||
autostart=true
|
||||
autorestart=true
|
||||
startretries=10
|
||||
user=signal-api
|
||||
directory=/usr/bin/
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/var/log/%s/out.log
|
||||
stderr_logfile=/var/log/%s/err.log
|
||||
stdout_logfile_maxbytes=50MB
|
||||
stdout_logfile_backups=10
|
||||
numprocs=1
|
||||
`
|
||||
|
||||
func main() {
|
||||
signalCliConfigDir := "/home/.local/share/signal-cli/"
|
||||
signalCliConfigDirEnv := utils.GetEnv("SIGNAL_CLI_CONFIG_DIR", "")
|
||||
@ -40,9 +23,10 @@ func main() {
|
||||
jsonRpc2ClientConfig := utils.NewJsonRpc2ClientConfig()
|
||||
|
||||
var tcpPort int64 = 6001
|
||||
|
||||
jsonRpc2ClientConfig.AddEntry(utils.MULTI_ACCOUNT_NUMBER, utils.JsonRpc2ClientConfigEntry{TcpPort: tcpPort})
|
||||
|
||||
args := []string{"--output=json", "--config", signalCliConfigDir}
|
||||
|
||||
signalCliBinary := "signal-cli"
|
||||
signalMode := utils.GetEnv("MODE", "json-rpc")
|
||||
if signalMode == "json-rpc-native" {
|
||||
@ -51,67 +35,53 @@ func main() {
|
||||
log.Fatal("The mode needs to be either 'json-rpc' or 'json-rpc-native'")
|
||||
}
|
||||
|
||||
signalCliIgnoreAttachments := ""
|
||||
ignoreAttachments := utils.GetEnv("JSON_RPC_IGNORE_ATTACHMENTS", "")
|
||||
if ignoreAttachments == "true" {
|
||||
signalCliIgnoreAttachments = " --ignore-attachments"
|
||||
}
|
||||
|
||||
signalCliIgnoreStories := ""
|
||||
ignoreStories := utils.GetEnv("JSON_RPC_IGNORE_STORIES", "")
|
||||
if ignoreStories == "true" {
|
||||
signalCliIgnoreStories = " --ignore-stories"
|
||||
}
|
||||
|
||||
signalCliIgnoreAvatars := ""
|
||||
ignoreAvatars := utils.GetEnv("JSON_RPC_IGNORE_AVATARS", "")
|
||||
if ignoreAvatars == "true" {
|
||||
signalCliIgnoreAvatars = " --ignore-avatars"
|
||||
}
|
||||
|
||||
signalCliIgnoreStickers := ""
|
||||
ignoreStickers := utils.GetEnv("JSON_RPC_IGNORE_STICKERS", "")
|
||||
if ignoreStickers == "true" {
|
||||
signalCliIgnoreStickers = " --ignore-stickers"
|
||||
}
|
||||
|
||||
supervisorctlProgramName := "signal-cli-json-rpc-1"
|
||||
supervisorctlLogFolder := "/var/log/" + supervisorctlProgramName
|
||||
_, err := exec.Command("mkdir", "-p", supervisorctlLogFolder).Output()
|
||||
if err != nil {
|
||||
log.Fatal("Couldn't create log folder ", supervisorctlLogFolder, ": ", err.Error())
|
||||
}
|
||||
|
||||
trustNewIdentities := ""
|
||||
trustNewIdentitiesEnv := utils.GetEnv("JSON_RPC_TRUST_NEW_IDENTITIES", "")
|
||||
if trustNewIdentitiesEnv == "on-first-use" {
|
||||
trustNewIdentities = " --trust-new-identities on-first-use"
|
||||
args = append(args, []string{"--trust-new-identities", "on-first-use"}...)
|
||||
} else if trustNewIdentitiesEnv == "always" {
|
||||
trustNewIdentities = " --trust-new-identities always"
|
||||
args = append(args, []string{"--trust-new-identities", "always"}...)
|
||||
} else if trustNewIdentitiesEnv == "never" {
|
||||
trustNewIdentities = " --trust-new-identities never"
|
||||
args = append(args, []string{"--trust-new-identities", "never"}...)
|
||||
} else if trustNewIdentitiesEnv != "" {
|
||||
log.Fatal("Invalid JSON_RPC_TRUST_NEW_IDENTITIES environment variable set!")
|
||||
}
|
||||
|
||||
log.Info("Updated jsonrpc2.yml")
|
||||
args = append(args, "daemon")
|
||||
|
||||
//write supervisorctl config
|
||||
supervisorctlConfigFilename := "/etc/supervisor/conf.d/" + "signal-cli-json-rpc-1.conf"
|
||||
|
||||
supervisorctlConfig := fmt.Sprintf(supervisorctlConfigTemplate, supervisorctlProgramName, supervisorctlProgramName, signalCliBinary,
|
||||
signalCliConfigDir, trustNewIdentities, signalCliIgnoreAttachments, signalCliIgnoreStories,
|
||||
signalCliIgnoreAvatars, signalCliIgnoreStickers, tcpPort,
|
||||
supervisorctlProgramName, supervisorctlProgramName)
|
||||
|
||||
err = ioutil.WriteFile(supervisorctlConfigFilename, []byte(supervisorctlConfig), 0644)
|
||||
if err != nil {
|
||||
log.Fatal("Couldn't write ", supervisorctlConfigFilename, ": ", err.Error())
|
||||
ignoreStories := utils.GetEnv("JSON_RPC_IGNORE_STORIES", "")
|
||||
if ignoreStories == "true" {
|
||||
args = append(args, "--ignore-stories")
|
||||
}
|
||||
|
||||
ignoreAvatars := utils.GetEnv("JSON_RPC_IGNORE_AVATARS", "")
|
||||
if ignoreAvatars == "true" {
|
||||
args = append(args, "--ignore-avatars")
|
||||
}
|
||||
|
||||
ignoreStickers := utils.GetEnv("JSON_RPC_IGNORE_STICKERS", "")
|
||||
if ignoreStickers == "true" {
|
||||
args = append(args, "--ignore-stickers")
|
||||
}
|
||||
|
||||
ignoreAttachments := utils.GetEnv("JSON_RPC_IGNORE_ATTACHMENTS", "")
|
||||
if ignoreAttachments == "true" {
|
||||
args = append(args, "--ignore-attachments")
|
||||
}
|
||||
|
||||
args = append(args, []string{"--tcp", "127.0.0.1:" + strconv.FormatInt(tcpPort, 10)}...)
|
||||
|
||||
// write jsonrpc.yml config file
|
||||
err = jsonRpc2ClientConfig.Persist(signalCliConfigDir + "jsonrpc2.yml")
|
||||
err := jsonRpc2ClientConfig.Persist(signalCliConfigDir + "jsonrpc2.yml")
|
||||
if err != nil {
|
||||
log.Fatal("Couldn't persist jsonrpc2.yaml: ", err.Error())
|
||||
}
|
||||
|
||||
log.Info("Updated jsonrpc2.yml")
|
||||
|
||||
env := os.Environ()
|
||||
|
||||
err = syscall.Exec("/usr/bin/"+signalCliBinary, args, env)
|
||||
if err != nil {
|
||||
log.Fatal("Couldn't start signal-cli in json-rpc mode: ", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user