基于 ubuntu 打包 mongodb docker 镜像

创建日期: 2025-01-13 11:02 | 作者: 风波 | 浏览次数: 23 | 分类: MongoDB

有个 github 仓库直接提供 mongodb 打包 docker 镜像需要的文件

仓库地址:https://github.com/docker-library/mongo 代码:mongo/8.0/

主要包括2个文件:Dockerfiledocker-entrypoint.sh

Dockerfile

#
# NOTE: THIS DOCKERFILE IS GENERATED VIA "apply-templates.sh"
#
# PLEASE DO NOT EDIT IT DIRECTLY.
#

FROM ubuntu:24.04

# add our user and group first to make sure their IDs get assigned consistently, regardless of whatever dependencies get added
RUN set -eux; \
    groupadd --gid 999 --system mongodb; \
    useradd --uid 999 --system --gid mongodb --home-dir /data/db mongodb; \
    mkdir -p /data/db /data/configdb; \
    chown -R mongodb:mongodb /data/db /data/configdb

RUN set -eux; \
    apt-get update; \
    apt-get install -y --no-install-recommends \
        ca-certificates \
        jq \
        numactl \
        procps \
    ; \
    rm -rf /var/lib/apt/lists/*

# grab gosu for easy step-down from root (https://github.com/tianon/gosu/releases)
ENV GOSU_VERSION 1.17
# grab "js-yaml" for parsing mongod's YAML config files (https://github.com/nodeca/js-yaml/releases)
ENV JSYAML_VERSION 3.13.1
ENV JSYAML_CHECKSUM 662e32319bdd378e91f67578e56a34954b0a2e33aca11d70ab9f4826af24b941

RUN set -eux; \
    \
    savedAptMark="$(apt-mark showmanual)"; \
    apt-get update; \
    apt-get install -y --no-install-recommends \
        gnupg \
        wget \
    ; \
    rm -rf /var/lib/apt/lists/*; \
    \
# download/install gosu
    dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
    wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
    wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
    export GNUPGHOME="$(mktemp -d)"; \
    gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
    gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
    gpgconf --kill all; \
    rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
    \
# download/install js-yaml
    mkdir -p /opt/js-yaml/; \
    wget -O /opt/js-yaml/js-yaml.tgz https://registry.npmjs.org/js-yaml/-/js-yaml-${JSYAML_VERSION}.tgz; \
    echo "$JSYAML_CHECKSUM */opt/js-yaml/js-yaml.tgz" | sha256sum -c -; \
    tar -xz --strip-components=1 -f /opt/js-yaml/js-yaml.tgz -C /opt/js-yaml package/dist/js-yaml.js package/package.json; \
    rm /opt/js-yaml/js-yaml.tgz; \
    ln -s /opt/js-yaml/dist/js-yaml.js /js-yaml.js; \
    \
# download/install MongoDB PGP keys
    export GNUPGHOME="$(mktemp -d)"; \
    wget -O KEYS 'https://pgp.mongodb.com/server-8.0.asc'; \
    gpg --batch --import KEYS; \
    mkdir -p /etc/apt/keyrings; \
    gpg --batch --export --armor '4B0752C1BCA238C0B4EE14DC41DE058A4E7DCA05' > /etc/apt/keyrings/mongodb.asc; \
    gpgconf --kill all; \
    rm -rf "$GNUPGHOME" KEYS; \
    \
    apt-mark auto '.*' > /dev/null; \
    apt-mark manual $savedAptMark > /dev/null; \
    apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \
    \
# smoke test
    chmod +x /usr/local/bin/gosu; \
    gosu --version; \
    gosu nobody true

RUN mkdir /docker-entrypoint-initdb.d

# Allow build-time overrides (eg. to build image with MongoDB Enterprise version)
# Options for MONGO_PACKAGE: mongodb-org OR mongodb-enterprise
# Options for MONGO_REPO: repo.mongodb.org OR repo.mongodb.com
# Example: docker build --build-arg MONGO_PACKAGE=mongodb-enterprise --build-arg MONGO_REPO=repo.mongodb.com .
ARG MONGO_PACKAGE=mongodb-org
ARG MONGO_REPO=repo.mongodb.org
ENV MONGO_PACKAGE=${MONGO_PACKAGE} MONGO_REPO=${MONGO_REPO}

ENV MONGO_MAJOR 8.0
RUN echo "deb [ signed-by=/etc/apt/keyrings/mongodb.asc ] http://$MONGO_REPO/apt/ubuntu noble/${MONGO_PACKAGE%-unstable}/$MONGO_MAJOR multiverse" | tee "/etc/apt/sources.list.d/${MONGO_PACKAGE%-unstable}.list"

# https://docs.mongodb.org/master/release-notes/8.0/
ENV MONGO_VERSION 8.0.4
# 11/27/2024, https://github.com/mongodb/mongo/tree/bc35ab4305d9920d9d0491c1c9ef9b72383d31f9

RUN set -x \
# installing "mongodb-enterprise" pulls in "tzdata" which prompts for input
    && export DEBIAN_FRONTEND=noninteractive \
    && apt-get update \
    && apt-get install -y \
        ${MONGO_PACKAGE}=$MONGO_VERSION \
        ${MONGO_PACKAGE}-server=$MONGO_VERSION \
        ${MONGO_PACKAGE}-shell=$MONGO_VERSION \
        ${MONGO_PACKAGE}-mongos=$MONGO_VERSION \
        ${MONGO_PACKAGE}-tools=$MONGO_VERSION \
        ${MONGO_PACKAGE}-database=$MONGO_VERSION \
        ${MONGO_PACKAGE}-database-tools-extra=$MONGO_VERSION \
    && rm -rf /var/lib/apt/lists/* \
    && rm -rf /var/lib/mongodb \
    && mv /etc/mongod.conf /etc/mongod.conf.orig

VOLUME /data/db /data/configdb

# ensure that if running as custom user that "mongosh" has a valid "HOME"
# https://github.com/docker-library/mongo/issues/524
ENV HOME /data/db

COPY docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT ["docker-entrypoint.sh"]

EXPOSE 27017
CMD ["mongod"]

docker-entrypoint.sh

#!/bin/bash
set -Eeuo pipefail

if [ "${1:0:1}" = '-' ]; then
    set -- mongod "$@"
fi

originalArgOne="$1"

# allow the container to be started with `--user`
# all mongo* commands should be dropped to the correct user
if [[ "$originalArgOne" == mongo* ]] && [ "$(id -u)" = '0' ]; then
    if [ "$originalArgOne" = 'mongod' ]; then
        find /data/configdb /data/db \! -user mongodb -exec chown mongodb '{}' +
    fi

    # make sure we can write to stdout and stderr as "mongodb"
    # (for our "initdb" code later; see "--logpath" below)
    chown --dereference mongodb "/proc/$$/fd/1" "/proc/$$/fd/2" || :
    # ignore errors thanks to https://github.com/docker-library/mongo/issues/149

    exec gosu mongodb "$BASH_SOURCE" "$@"
fi

dpkgArch="$(dpkg --print-architecture)"
case "$dpkgArch" in
    amd64) # https://github.com/docker-library/mongo/issues/485#issuecomment-891991814
        if ! grep -qE '^flags.* avx( .*|$)' /proc/cpuinfo; then
            {
                echo
                echo 'WARNING: MongoDB 5.0+ requires a CPU with AVX support, and your current system does not appear to have that!'
                echo '  see https://jira.mongodb.org/browse/SERVER-54407'
                echo '  see also https://www.mongodb.com/community/forums/t/mongodb-5-0-cpu-intel-g4650-compatibility/116610/2'
                echo '  see also https://github.com/docker-library/mongo/issues/485#issuecomment-891991814'
                echo
            } >&2
        fi
        ;;

    arm64) # https://github.com/docker-library/mongo/issues/485#issuecomment-970864306
        # https://en.wikichip.org/wiki/arm/armv8#ARMv8_Extensions_and_Processor_Features
        # http://javathunderx.blogspot.com/2018/11/cheat-sheet-for-cpuinfo-features-on.html
        if ! grep -qE '^Features.* (fphp|dcpop|sha3|sm3|sm4|asimddp|sha512|sve)( .*|$)' /proc/cpuinfo; then
            {
                echo
                echo 'WARNING: MongoDB requires ARMv8.2-A or higher, and your current system does not appear to implement any of the common features for that!'
                echo '  applies to all versions ≥5.0, any of 4.4 ≥4.4.19'
                echo '  see https://jira.mongodb.org/browse/SERVER-71772'
                echo '  see https://jira.mongodb.org/browse/SERVER-55178'
                echo '  see also https://en.wikichip.org/wiki/arm/armv8#ARMv8_Extensions_and_Processor_Features'
                echo '  see also https://github.com/docker-library/mongo/issues/485#issuecomment-970864306'
                echo
            } >&2
        fi
        ;;
esac

# you should use numactl to start your mongod instances, including the config servers, mongos instances, and any clients.
# https://docs.mongodb.com/manual/administration/production-notes/#configuring-numa-on-linux
if [[ "$originalArgOne" == mongo* ]]; then
    numa='numactl --interleave=all'
    if $numa true &> /dev/null; then
        set -- $numa "$@"
    fi
fi

# usage: file_env VAR [DEFAULT]
#    ie: file_env 'XYZ_DB_PASSWORD' 'example'
# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of
#  "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature)
file_env() {
    local var="$1"
    local fileVar="${var}_FILE"
    local def="${2:-}"
    if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then
        echo >&2 "error: both $var and $fileVar are set (but are exclusive)"
        exit 1
    fi
    local val="$def"
    if [ "${!var:-}" ]; then
        val="${!var}"
    elif [ "${!fileVar:-}" ]; then
        val="$(< "${!fileVar}")"
    fi
    export "$var"="$val"
    unset "$fileVar"
}

# see https://github.com/docker-library/mongo/issues/147 (mongod is picky about duplicated arguments)
_mongod_hack_have_arg() {
    local checkArg="$1"; shift
    local arg
    for arg; do
        case "$arg" in
            "$checkArg"|"$checkArg"=*)
                return 0
                ;;
        esac
    done
    return 1
}
# _mongod_hack_get_arg_val '--some-arg' "$@"
_mongod_hack_get_arg_val() {
    local checkArg="$1"; shift
    while [ "$#" -gt 0 ]; do
        local arg="$1"; shift
        case "$arg" in
            "$checkArg")
                echo "$1"
                return 0
                ;;
            "$checkArg"=*)
                echo "${arg#$checkArg=}"
                return 0
                ;;
        esac
    done
    return 1
}
declare -a mongodHackedArgs
# _mongod_hack_ensure_arg '--some-arg' "$@"
# set -- "${mongodHackedArgs[@]}"
_mongod_hack_ensure_arg() {
    local ensureArg="$1"; shift
    mongodHackedArgs=( "$@" )
    if ! _mongod_hack_have_arg "$ensureArg" "$@"; then
        mongodHackedArgs+=( "$ensureArg" )
    fi
}
# _mongod_hack_ensure_no_arg '--some-unwanted-arg' "$@"
# set -- "${mongodHackedArgs[@]}"
_mongod_hack_ensure_no_arg() {
    local ensureNoArg="$1"; shift
    mongodHackedArgs=()
    while [ "$#" -gt 0 ]; do
        local arg="$1"; shift
        if [ "$arg" = "$ensureNoArg" ]; then
            continue
        fi
        mongodHackedArgs+=( "$arg" )
    done
}
# _mongod_hack_ensure_no_arg '--some-unwanted-arg' "$@"
# set -- "${mongodHackedArgs[@]}"
_mongod_hack_ensure_no_arg_val() {
    local ensureNoArg="$1"; shift
    mongodHackedArgs=()
    while [ "$#" -gt 0 ]; do
        local arg="$1"; shift
        case "$arg" in
            "$ensureNoArg")
                shift # also skip the value
                continue
                ;;
            "$ensureNoArg"=*)
                # value is already included
                continue
                ;;
        esac
        mongodHackedArgs+=( "$arg" )
    done
}
# _mongod_hack_ensure_arg_val '--some-arg' 'some-val' "$@"
# set -- "${mongodHackedArgs[@]}"
_mongod_hack_ensure_arg_val() {
    local ensureArg="$1"; shift
    local ensureVal="$1"; shift
    _mongod_hack_ensure_no_arg_val "$ensureArg" "$@"
    mongodHackedArgs+=( "$ensureArg" "$ensureVal" )
}

# _js_escape 'some "string" value'
_js_escape() {
    jq --null-input --arg 'str' "$1" '$str'
}

: "${TMPDIR:=/tmp}"
jsonConfigFile="$TMPDIR/docker-entrypoint-config.json"
tempConfigFile="$TMPDIR/docker-entrypoint-temp-config.json"
_parse_config() {
    if [ -s "$tempConfigFile" ]; then
        return 0
    fi

    local configPath
    if configPath="$(_mongod_hack_get_arg_val --config "$@")" && [ -s "$configPath" ]; then
        # if --config is specified, parse it into a JSON file so we can remove a few problematic keys (especially SSL-related keys)
        # see https://docs.mongodb.com/manual/reference/configuration-options/
        if grep -vEm1 '^[[:space:]]*(#|$)' "$configPath" | grep -qE '^[[:space:]]*[^=:]+[[:space:]]*='; then
            # if the first non-comment/non-blank line of the config file looks like "foo = ...", this is probably the 2.4 and older "ini-style config format"
            # mongod tries to parse config as yaml and then falls back to ini-style parsing
            # https://github.com/mongodb/mongo/blob/r6.0.3/src/mongo/util/options_parser/options_parser.cpp#L1883-L1894
            echo >&2
            echo >&2 "WARNING: it appears that '$configPath' is in the older INI-style format (replaced by YAML in MongoDB 2.6)"
            echo >&2 '  This script does not parse the older INI-style format, and thus will ignore it.'
            echo >&2
            return 1
        fi
        if [ "$mongoShell" = 'mongo' ]; then
            "$mongoShell" --norc --nodb --quiet --eval "load('/js-yaml.js'); printjson(jsyaml.load(cat($(_js_escape "$configPath"))))" > "$jsonConfigFile"
        else
            # https://www.mongodb.com/docs/manual/reference/method/js-native/#std-label-native-in-mongosh
            "$mongoShell" --norc --nodb --quiet --eval "load('/js-yaml.js'); JSON.stringify(jsyaml.load(fs.readFileSync($(_js_escape "$configPath"), 'utf8')))" > "$jsonConfigFile"
        fi
        if [ "$(head -c1 "$jsonConfigFile")" != '{' ] || [ "$(tail -c2 "$jsonConfigFile")" != '}' ]; then
            # if the file doesn't start with "{" and end with "}", it's *probably* an error ("uncaught exception: YAMLException: foo" for example), so we should print it out
            echo >&2 'error: unexpected "js-yaml.js" output while parsing config:'
            cat >&2 "$jsonConfigFile"
            exit 1
        fi
        jq 'del(.systemLog, .processManagement, .net, .security, .replication)' "$jsonConfigFile" > "$tempConfigFile"
        return 0
    fi

    return 1
}
dbPath=
_dbPath() {
    if [ -n "$dbPath" ]; then
        echo "$dbPath"
        return
    fi

    if ! dbPath="$(_mongod_hack_get_arg_val --dbpath "$@")"; then
        if _parse_config "$@"; then
            dbPath="$(jq -r '.storage.dbPath // empty' "$jsonConfigFile")"
        fi
    fi

    if [ -z "$dbPath" ]; then
        if _mongod_hack_have_arg --configsvr "$@" || {
            _parse_config "$@" \
            && clusterRole="$(jq -r '.sharding.clusterRole // empty' "$jsonConfigFile")" \
            && [ "$clusterRole" = 'configsvr' ]
        }; then
            # if running as config server, then the default dbpath is /data/configdb
            # https://docs.mongodb.com/manual/reference/program/mongod/#cmdoption-mongod-configsvr
            dbPath=/data/configdb
        fi
    fi

    : "${dbPath:=/data/db}"

    echo "$dbPath"
}

if [ "$originalArgOne" = 'mongod' ]; then
    file_env 'MONGO_INITDB_ROOT_USERNAME'
    file_env 'MONGO_INITDB_ROOT_PASSWORD'

    mongoShell='mongo'
    if ! command -v "$mongoShell" > /dev/null; then
        mongoShell='mongosh'
    fi

    # pre-check a few factors to see if it's even worth bothering with initdb
    shouldPerformInitdb=
    if [ "$MONGO_INITDB_ROOT_USERNAME" ] && [ "$MONGO_INITDB_ROOT_PASSWORD" ]; then
        # if we have a username/password, let's set "--auth"
        _mongod_hack_ensure_arg '--auth' "$@"
        set -- "${mongodHackedArgs[@]}"
        shouldPerformInitdb='true'
    elif [ "$MONGO_INITDB_ROOT_USERNAME" ] || [ "$MONGO_INITDB_ROOT_PASSWORD" ]; then
        cat >&2 <<-'EOF'

            error: missing 'MONGO_INITDB_ROOT_USERNAME' or 'MONGO_INITDB_ROOT_PASSWORD'
                   both must be specified for a user to be created

        EOF
        exit 1
    fi

    if [ -z "$shouldPerformInitdb" ]; then
        # if we've got any /docker-entrypoint-initdb.d/* files to parse later, we should initdb
        for f in /docker-entrypoint-initdb.d/*; do
            case "$f" in
                *.sh|*.js) # this should match the set of files we check for below
                    shouldPerformInitdb="$f"
                    break
                    ;;
            esac
        done
    fi

    # check for a few known paths (to determine whether we've already initialized and should thus skip our initdb scripts)
    if [ -n "$shouldPerformInitdb" ]; then
        dbPath="$(_dbPath "$@")"
        for path in \
            "$dbPath/WiredTiger" \
            "$dbPath/journal" \
            "$dbPath/local.0" \
            "$dbPath/storage.bson" \
        ; do
            if [ -e "$path" ]; then
                shouldPerformInitdb=
                break
            fi
        done
    fi

    if [ -n "$shouldPerformInitdb" ]; then
        mongodHackedArgs=( "$@" )
        if _parse_config "$@"; then
            _mongod_hack_ensure_arg_val --config "$tempConfigFile" "${mongodHackedArgs[@]}"
        fi
        _mongod_hack_ensure_arg_val --bind_ip 127.0.0.1 "${mongodHackedArgs[@]}"
        _mongod_hack_ensure_arg_val --port 27017 "${mongodHackedArgs[@]}"
        _mongod_hack_ensure_no_arg --bind_ip_all "${mongodHackedArgs[@]}"

        # remove "--auth" and "--replSet" for our initial startup (see https://docs.mongodb.com/manual/tutorial/enable-authentication/#start-mongodb-without-access-control)
        # https://github.com/docker-library/mongo/issues/211
        _mongod_hack_ensure_no_arg --auth "${mongodHackedArgs[@]}"
        # "keyFile implies security.authorization"
        # https://docs.mongodb.com/manual/reference/configuration-options/#mongodb-setting-security.keyFile
        _mongod_hack_ensure_no_arg_val --keyFile "${mongodHackedArgs[@]}"
        if [ "$MONGO_INITDB_ROOT_USERNAME" ] && [ "$MONGO_INITDB_ROOT_PASSWORD" ]; then
            _mongod_hack_ensure_no_arg_val --replSet "${mongodHackedArgs[@]}"
        fi

        # "BadValue: need sslPEMKeyFile when SSL is enabled" vs "BadValue: need to enable SSL via the sslMode flag when using SSL configuration parameters"
        tlsMode='disabled'
        if _mongod_hack_have_arg '--tlsCertificateKeyFile' "$@"; then
            tlsMode='allowTLS'
        fi
        _mongod_hack_ensure_arg_val --tlsMode "$tlsMode" "${mongodHackedArgs[@]}"

        if stat "/proc/$$/fd/1" > /dev/null && [ -w "/proc/$$/fd/1" ]; then
            # https://github.com/mongodb/mongo/blob/38c0eb538d0fd390c6cb9ce9ae9894153f6e8ef5/src/mongo/db/initialize_server_global_state.cpp#L237-L251
            # https://github.com/docker-library/mongo/issues/164#issuecomment-293965668
            _mongod_hack_ensure_arg_val --logpath "/proc/$$/fd/1" "${mongodHackedArgs[@]}"
        else
            initdbLogPath="$(_dbPath "$@")/docker-initdb.log"
            echo >&2 "warning: initdb logs cannot write to '/proc/$$/fd/1', so they are in '$initdbLogPath' instead"
            _mongod_hack_ensure_arg_val --logpath "$initdbLogPath" "${mongodHackedArgs[@]}"
        fi
        _mongod_hack_ensure_arg --logappend "${mongodHackedArgs[@]}"

        pidfile="$TMPDIR/docker-entrypoint-temp-mongod.pid"
        rm -f "$pidfile"
        _mongod_hack_ensure_arg_val --pidfilepath "$pidfile" "${mongodHackedArgs[@]}"

        "${mongodHackedArgs[@]}" --fork

        mongo=( "$mongoShell" --host 127.0.0.1 --port 27017 --quiet )

        # check to see that our "mongod" actually did start up (catches "--help", "--version", slow prealloc, etc)
        # https://jira.mongodb.org/browse/SERVER-16292
        tries=30
        while true; do
            if ! { [ -s "$pidfile" ] && ps "$(< "$pidfile")" &> /dev/null; }; then
                # bail ASAP if "mongod" isn't even running
                echo >&2
                echo >&2 "error: $originalArgOne does not appear to have stayed running -- perhaps it had an error?"
                echo >&2
                exit 1
            fi
            if "${mongo[@]}" 'admin' --eval 'quit(0)' &> /dev/null; then
                # success!
                break
            fi
            (( tries-- ))
            if [ "$tries" -le 0 ]; then
                echo >&2
                echo >&2 "error: $originalArgOne does not appear to have accepted connections quickly enough -- perhaps it had an error?"
                echo >&2
                exit 1
            fi
            sleep 1
        done

        if [ "$MONGO_INITDB_ROOT_USERNAME" ] && [ "$MONGO_INITDB_ROOT_PASSWORD" ]; then
            rootAuthDatabase='admin'

            "${mongo[@]}" "$rootAuthDatabase" <<-EOJS
                db.createUser({
                    user: $(_js_escape "$MONGO_INITDB_ROOT_USERNAME"),
                    pwd: $(_js_escape "$MONGO_INITDB_ROOT_PASSWORD"),
                    roles: [ { role: 'root', db: $(_js_escape "$rootAuthDatabase") } ]
                })
            EOJS
        fi

        export MONGO_INITDB_DATABASE="${MONGO_INITDB_DATABASE:-test}"

        echo
        for f in /docker-entrypoint-initdb.d/*; do
            case "$f" in
                *.sh) echo "$0: running $f"; . "$f" ;;
                *.js) echo "$0: running $f"; "${mongo[@]}" "$MONGO_INITDB_DATABASE" "$f"; echo ;;
                *)    echo "$0: ignoring $f" ;;
            esac
            echo
        done

        "${mongodHackedArgs[@]}" --shutdown
        rm -f "$pidfile"

        echo
        echo 'MongoDB init process complete; ready for start up.'
        echo
    fi

    # MongoDB defaults to localhost-only binding
    haveBindIp=
    if _mongod_hack_have_arg --bind_ip "$@" || _mongod_hack_have_arg --bind_ip_all "$@"; then
        haveBindIp=1
    elif _parse_config "$@" && jq --exit-status '.net.bindIp // .net.bindIpAll' "$jsonConfigFile" > /dev/null; then
        haveBindIp=1
    fi
    if [ -z "$haveBindIp" ]; then
        # so if no "--bind_ip" is specified, let's add "--bind_ip_all"
        set -- "$@" --bind_ip_all
    fi

    unset "${!MONGO_INITDB_@}"
fi

rm -f "$jsonConfigFile" "$tempConfigFile"

exec "$@"
23 浏览
13 爬虫
0 评论