matrix-eno-bot icon indicating copy to clipboard operation
matrix-eno-bot copied to clipboard

Docker container is stuck at "Performing initial database setup..."

Open minnixtx opened this issue 2 years ago • 12 comments

Running on debian bullseye amd64. When I start the container via "docker compose up" after building it I get this message over and over

matrix-eno-bot  | 2022-10-07 17:31:20,115 | storage [INFO] Performing initial database setup...
matrix-eno-bot exited with code 1

Below are my configs:

commands.yaml

#
# ***** Initialization *****
# This is an example command dictionary configuration. Mind you, this is an
# example configuration meant to convey the structure of the configuration
# file. It will probably not work as is in your environment. 
# So, copy this file to "commands.yaml". Then modify and adjust 
# "commands.yaml" to suit your needs. E.g. On Linux you would do

# $ cp commands.yaml.example commands.yaml
# $ nano commands.yaml # adjust to your needs, especially the paths!


# ***** Paths *****

# The bot needs to find your commands. For that the bot uses the PATH 
# variable. By adjusting the "paths" values below you can add additional
# paths to the system PATH variable. If the bot is using commands that
# are not already on the system PATH, you must add the additional command
# paths below. 
# Furthermore, make sure the bot has read and execute access
# to the commands, otherwise they won't work.
# Here we show two example command paths. Adjust them as needed.
paths:
  - /home/minnix/.local/lib/matrix-eno-bot/eno/scripts
  - /home/minnix/.local/etc/matrix-eno-bot/bin


# ***** Commands *****

# Commands are a list of specifications for your bot commands.
# The children of the "commands" key are the command names. 
# Command names are nick names like "alert" or "backup". 
# Each command, listed by its name, has the following 
# keys (or properties):
#
#   cmd:               string: name of the command, executable, or script.
#                      The bot will attempt to execute this command when
#                      triggered. The bot will call this "cmd" and pass 
#                      the given arguments to it.
#                      Example: "date" (a command provided by the OS)
#   help:              string: help string explaining the command.
#                      This is useful when you forget how use the command.
#                      The help string provided will be listed and shown with
#                      the 'help' command of the bot.
#                      Example: "returns the current date of the server"
#   regex:             string: regex pattern whose matches are valid ways to call the 
#                      command.
#                      If a bot message matches a regular expression, then the
#                      corresponding command will be executed. 
#                      If a bot message matched multiple regular expressions, 
#                      only the first matching command will be executed!
#                      It is recommended that the regular expressions are used
#                      such that they are mutually exclusive to avoid
#                      situation where one messages matches multiple regular
#                      expressions.
#                      When the "regex" matches the "cmd" will be triggered.
#                      Example: "^date$" (only the exact string of "date"
#                      will match) 
#   markdown_convert:  true or false: Specifies whether the message reply has
#                      been formatted in markdown.
#                      The bot will convert this markdown-formatted input
#                      and convert it into an HTML-like format understood by
#                      Matrix, so that the bot reply shows up visually as
#                      nice as the markdown input.
#                      Defaults to true.
#                      Example: Your command output is a markdown formatted
#                      string such as "I *really* like it!" Set 
#                      markdown_convert to true and the receiver gets the
#                      text "I really like it!" with the word "really"
#                      visually in italic. In short, use "true", whenever
#                      your command output is a markdown-formatted string;
#                      false otherwise.
#   formatted:         true or false: Specifies whether message reply will
#                      be sent as a formatted message.
#                      Defaults to true.
#   code:              true or false: Specifies whether message reply will
#                      be formatted as a code block with fixed-size font.
#                      If set to "true", "markdown_convert" will be ignored.
#                      Defaults to false.
#   split:             string: if this string is set, splits the message reply
#                      into multiple messages wherever the string specified in
#                      split occurs.
#                      Defaults to None, no message splitting by default.
#                      Example: "\n\n\n" (Wherever the command output contains 
#                      two empty lines, the output will be split. Each piece
#                      will be sent as a separate reply message.
commands:

  # There are 3 kinds of commands.
  # a) built-in commands
  # b) pre-packed commands provided by the matrix-eno-bot repo
  # c) your custom commands that you can add
  #    To add your custom commands go to the end of file, you will
  #    find a "Custom commands" header there. Add them there.

  # Built-in commands
  # ---------------
  # help : "help" is a reserved word, so don't use it as a custom command!
  # reload : "reload" is a reserved word, so don't use it as a custom command!


  # Pre-packed commands provided by the matrix-eno-bot repo
  # -------------------------------------------------------

  # alert if too many resources are used, best to use with cron
  alert:
    regex:             "alert$|^alert .*$|^alarm$|^alarm .*|^alert.sh$"
    cmd:               alert.sh
    help:              shows if any CPU, RAM, or disk thresholds have been exceeded
    markdown_convert:  false
    formatted:         true
    code:              true
  # perform a backup to disk
  backup:
    regex:             "^backup$|^backup .*$|^backup.sh$"
    cmd:               backup.sh
    help:              performs backup on server
    markdown_convert:  false
    formatted:         true
    code:              true
  # get BTC ticker
  btc:
    regex:             "^btc$|^btc .*$|^bitcoin$|^btc.sh$"
    cmd:               btc.sh
    help:              gives Bitcoin BTC price info
    markdown_convert:  false
    formatted:         true
    code:              true
  # get cheatsheets, see https://github.com/cheat/cheat
  cheatsheet:
    regex:             "^cheat$|^cheatsheet$|^chuleta$|^cheat.sh$|^cheat .*$|^cheatsheet .*$|^chuleta .*$|^cheat.sh .*$"
    cmd:               cheat
    help:              get cheatsheets, see https://github.com/cheat/cheat
    markdown_convert:  false
    formatted:         true
    code:              true
  # check status and look for updates
  # see also: upgrade
  check:
    regex:             "^check$|^chk$|^status$|^state$|^check .*$|^chk .*|^status .*$|^state .*$|^check.sh$|^check.sh .*"
    cmd:               check.sh
    help:              check status, health status, updates, etc.
    markdown_convert:  false
    formatted:         true
    code:              false
  # CPU temperature, to monitor the CPU temperatures
  cputemp:
    regex:             "^cpu$|^temp$|^temperature$|^celsius$|^cputemp.*$|^hot$|^chaud$"
    cmd:               cputemp.sh
    help:              give the current CPU temperatures
    markdown_convert:  false
    formatted:         true
    code:              false
  # get date and time
  datetime:
    regex:             "^date$|^time$|^tiempo$|^hora$|^temps$|^heure$|^heures$|^datum$|^zeit$|^datetime.sh$"
    cmd:               datetime.sh
    help:              give current date and time of server
    markdown_convert:  false
    formatted:         true
    code:              true
  # duckduckgo
  ddg:
    regex:             "^ddg$|^ddg .*$|^duck$|^duck .*$|^duckduckgo$|^duckduckgo .*$|^search$|^search .*|^ddg.sh$|^ddg.sh .*"
    cmd:               ddg.sh
    help:              search the web with DuckDuckGo search
    markdown_convert:  false
    formatted:         true
    code:              false
  # disk space, monitor disk space
  disks:
    regex:             "^disks$|^disk$|^full$|^space$|^disks.sh$"
    cmd:               disks.sh
    help:              see how full your disks or mountpoints are
    markdown_convert:  false
    formatted:         true
    code:              true
  # echo, trivial example to have the bot respond
  echo:
    regex:             "^echo$|^echo .*"
    cmd:               echo.py
    help:              bot echoes back your input
    markdown_convert:  false
    formatted:         true
    code:              false
  # get ETH ticker
  eth:
    regex:             "^eth$|^eth .*$|^ethereum$|^eth.sh$"
    cmd:               eth.sh
    help:              gives Ethereum price info
    markdown_convert:  false
    formatted:         true
    code:              true
  # get firewall settings
  firewall:
    regex:             "^firewall$|^fw$|^firewall .*$|^firewall.sh$"
    cmd:               firewall.sh
    help:              list the firewall settings and configuration
    markdown_convert:  false
    formatted:         true
    code:              true
  # get a compliment, hello
  hello:
    regex:             "^salut$|^ciao$|^hallo$|^hi$|^servus$|^hola$|^hello$|^hello .*$|^bonjour$|^bonne nuit$|^hello.sh$"
    cmd:               hello.sh
    help:              gives you a friendly compliment
    markdown_convert:  false
    formatted:         true
    code:              false
  # Hacker News
  hn:
    regex:             "^hn$|^hn .*$|^hn.sh$|^hn.sh .*"
    cmd:               hn.sh
    help:              read Hacker News, fetches front page headlines from Hacker News
    markdown_convert:  false
    formatted:         true
    code:              false
  # Messari News
  mn:
    regex:             "^mn$|^mn .*$|^mn.sh$|^mn.sh .*"
    cmd:               mn.sh
    help:              read Messari News, fetches the latest news articles from Messari
    markdown_convert:  false
    formatted:         true
    code:              false
    split:             "\n\n\n"
  # message of the day
  motd:
    regex:             "^motd|^motd .*|^motd.sh$"
    cmd:               motd.sh
    help:              gives you the Linux Message Of The Day
  # platform info
  platforminfo:
    regex:             "^platform$|^platform .*|^platforminfo.py$"
    cmd:               platforminfo.py
    help:              give hardware and operating system platform information
  # ps, host status
  ps:
    regex:             "^ps$|^ps .*|^ps.sh$"
    cmd:               ps.sh
    help:              print current CPU, RAM and Disk utilization of server
    markdown_convert:  false
    formatted:         true
    code:              true
  # restart, reset
  restart:
    regex:             "^restart$|^reset$|^restart .*$|^reset .*$|^restart.sh$|^restart.sh .*"
    cmd:               restart.sh
    help:              restart the bot itself, or Matrix services
    markdown_convert:  false
    formatted:         true
    code:              false
  # RSS
  rss:
    regex:             "^rss$|^feed$|^rss .*$|^feed .*$|^rss.sh$|^rss.sh .*"
    cmd:               rss.sh
    help:              read RSS feeds
    markdown_convert:  false
    formatted:         true
    code:              false
    split:             "\n\n\n"
  # Stock-to-flow
  s2f:
    regex:             "^s2f$|^mys2f.py.*|^flow$|^s2f|^flow .*$|^s2f .$|^s-to-f$|^stock-to-flow .*$|^eyf$|^eyf .*$|^e-y-f$"
    cmd:               s2f.sh
    help:              give Stock-to-flow info
    markdown_convert:  false
    formatted:         true
    code:              true
  # tides
  tides:
    regex:             "^tide$|^tides$|^marea|^mareas|^tide .*$|^tides .*$|^marea .*$|^mareas .*$|^gehzeiten .*$|^tides.sh$|^tides.sh .*"
    cmd:               tides.sh
    help:              give tidal forecast
    markdown_convert:  false
    formatted:         true
    code:              false
  # top CPU, MEM consumers
  top:
    regex:             "^top$|^top .*|^top.sh$|^top.sh .*"
    cmd:               top.sh
    help:              list 5 top CPU and RAM consuming processes
    markdown_convert:  false
    formatted:         true
    code:              true
  # get TOTP 2FA pin
  totp:
    regex:             "^otp$|^totp$|^otp .*$|^totp .*$"
    cmd:               totp.sh
    help:              get 2FA Two-factor-authentication TOTP PIN via bot message
    markdown_convert:  false
    formatted:         true
    code:              false
  # twitter
  twitter:
    regex:             "^tweet$|^twitter$|^tweet .*$|^twitter .*$|^twitter.sh$|^twitter.sh .*"
    cmd:               twitter.sh
    help:              read latest user tweets from Twitter
    markdown_convert:  false
    formatted:         true
    code:              false
  # update components
  update:
    regex:             "^update$|^upgrade$|^update .*$|^upgrade .*$|^update.sh$|^update.sh .*"
    cmd:               update.sh
    help:              update operating sytem
    markdown_convert:  false
    formatted:         true
    code:              false
  # list matrix users by issuing a REST API query
  users:
    regex:             "^usr$|^user$|^users$|^users .*$|^users.sh$"
    cmd:               users.sh
    help:              list registered Matrix users
    markdown_convert:  false
    formatted:         true
    code:              true
  # wake up PC via wake-on-LAN
  wake:
    regex:             "^wake$|^wakeup$|^wake .*$|^wakeup .*$|^wakelan .*$|^wake.sh$|^wake .*"
    cmd:               wake.sh
    help:              wake up another PC via LAN
    markdown_convert:  false
    formatted:         true
    code:              false
  # waves and surf conditions
  # see also: tides
  waves:
    regex:             "^wave$|^waves$|^wave .*$|^waves .*$|^surf$|^surf .*$|^waves.sh$"
    cmd:               waves.sh
    help:              give waves and surf forecast
    markdown_convert:  false
    formatted:         true
    code:              true
  # get weather forecast
  weather:
    regex:             "^weather$|^tiempo$|^wetter$|^temps$|^weather .*$|^tiempo .*$|^eltiempo .*$|^wetter .*$|^temps .*$|^weather.sh$|^weather.sh .*"
    cmd:               weather.sh
    help:              give weather forecast
    markdown_convert:  false
    formatted:         true
    code:              true
  # fetch web pages
  web:
    regex:             "^www$|^web$|^web .*$|^www .*$|^browse$|^browse .*|^web.sh$|^web.sh .*"
    cmd:               web.sh
    help:              surf the web, get a web page (JavaScript pages not supported)
    markdown_convert:  false
    formatted:         true
    code:              false
  # whoami
  whoami:
    regex:             "^w$|^who$|^whoami$"
    cmd:               whoami.py
    help:              return information about the user, whose unix account is running the bot
    markdown_convert:  false
    formatted:         true
    code:              false


  # Custom commands
  # ---------------

  # add your custom commands here
  
# End of commands configuration file

config.yaml

# Welcome to the sample config file
# Below you will find various config sections and options
# Default values are shown

# The string to prefix messages with to talk to the bot in group chats
# Changed from original "!c" to "1z! because on cell phone "!c" seems too
# complicated.
command_prefix: "1z"

# Options for connecting to the bot's Matrix account
matrix:
  # The Matrix User ID of the bot account
  user_id: "@lugbot:minnix.dev"
  # Matrix account password
  user_password: "<password>"

  # Matrix account access token
  # To create a new Matrix device for the bot, use and set the `user_password`
  # field. Once the device exists, you can optionally replace the
  # `user_password` with the `access_token` field. Using the `access_token`
  # field is slightly safer as it does not expose the password of the bot
  # account. You can use only one or the other: either use the `user_password`
  # or the `access_token` field. You can find the access token in the Matrix
  # client where you have registered the bot account or you can find it in
  # the bot log file. If the logging options are set accordingly the access
  # token will be logged to the bot log file.
  # Default: commented out
  # access_token: "PutYourLongAccessTokenHere"

  # The URL of the homeserver to connect to
  homeserver_url: https://matrix.minnix.dev
  # The device ID that is **non pre-existing** device
  # If this device ID already exists, messages will be dropped
  # silently in encrypted rooms
  device_id: lugbot_serverid
  # What to name the device? Often referred to as device name or display name.
  device_name: lugbot
  # Should the bot trust all the devices of its own Matrix account?
  # Default: false
  # If false, nothing is done. After login, no device will be automatically
  # trusted.
  # If true, once at startup, after logging in, the bot device will
  # automatically establish trust to all other devices of the bot account.
  trust_own_devices: false
  # Do you want to change the device_name of the already existing bot?
  # Default: false
  # If false, nothing is done. After creation, device_name will be ignored.
  # If true, device_name of bot will be changed to value given in device_name.
  change_device_name: false
  # encrytion is enabled by default

storage:
  # The path to the database
  database_filepath: "bot.db"
  # The path to a directory for internal bot storage
  # containing encryption keys, sync tokens, etc.
  store_filepath: "./store"
  # The path to the command dictionary configuration file
  command_dict_filepath: "./commands.yaml"

# Logging setup
logging:
  # Logging level
  # Allowed levels are 'INFO', 'WARNING', 'ERROR', 'DEBUG'
  # where DEBUG is most verbose
  level: INFO
  # Configure logging to a file
  file_logging:
    # Whether logging to a file is enabled
    enabled: false
    # The path to the file to log to. May be relative or absolute
    filepath: bot.log
  # Configure logging to the console output
  console_logging:
    # Whether logging to the console is enabled
    enabled: true

docker-compose.yaml

version: '3'                                                                                                                                             
                                                                                                                                                         
services:                                                                                                                                                
  matrix-eno-bot:                                                                                                                                        
    container_name: matrix-eno-bot                                                                                                                       
    image: 'matrix-eno-bot:latest'                                                                                                                       
    build: '.'                                                                                                                                           
    restart: always                                                                                                                                      
    volumes:                                                                                                                                             
      - /home/minnix/lugbot/matrix-eno-bot/config.yaml:/bot/config.yaml                                                                                  
      - /home/minnix/lugbot/matrix-eno-bot/commands.yaml:/bot/commands.yaml                                                                              
      - /home/minnix/lugbot/matrix-eno-bot/bot.db:/bot/bot.db                                                                                            
      - /home/minnix/lugbot/matrix-eno-bot/store/:/bot/store/                                                                                            
    stop_signal: SIGINT                     

Any help would be appreciated thanks.

minnixtx avatar Oct 07 '22 21:10 minnixtx

I do not use docker frequently, so I cannot be of help.

Anyone else out there who can assist or provide some feedback? Please comment.

8go avatar Oct 08 '22 16:10 8go

Also:

vim config.yaml # modify the logging parameters, increase logging

See in the log output helps you further.

8go avatar Oct 08 '22 16:10 8go

Also:

vim config.yaml # modify the logging parameters, increase logging

See in the log output helps you further.

Ok I changed to DEBUG instead of INFO and was able to get a little more info:

matrix-eno-bot  | 2022-10-10 08:24:10,313 | storage [INFO] Performing initial database setup...
matrix-eno-bot  | 2022-10-10 08:24:10,314 | __main__ [DEBUG] Traceback (most recent call last):
matrix-eno-bot  |   File "./main.py", line 169, in <module>
matrix-eno-bot  |     asyncio.get_event_loop().run_until_complete(main())
matrix-eno-bot  |   File "/usr/local/lib/python3.7/asyncio/base_events.py", line 587, in run_until_complete
matrix-eno-bot  |     return future.result()
matrix-eno-bot  |   File "./main.py", line 59, in main
matrix-eno-bot  |     store = Storage(config.database_filepath)
matrix-eno-bot  |   File "/bot/storage.py", line 26, in __init__
matrix-eno-bot  |     self._initial_setup()
matrix-eno-bot  |   File "/bot/storage.py", line 33, in _initial_setup
matrix-eno-bot  |     self.conn = sqlite3.connect(self.db_path)
matrix-eno-bot  | sqlite3.OperationalError: unable to open database file
matrix-eno-bot  | 
matrix-eno-bot exited with code 1

minnixtx avatar Oct 10 '22 08:10 minnixtx

The debug log says: sqlite3 tries to create a database and it fails. wherever your bot.db there is a problem. Either the file exists and is not a real sqlite3 db, or the dir is not writeable, etc.

As a detail test: test that bot.db does not exist, but that the location is writeable. So, check if a file is there. Do a cat to create a tiny file, check file, (this verifies you can write), then remove the file (verify that it does not exist), then start bot.

Alternatively, run it without docker to learn more about it and then debug your docker yaml.

8go avatar Oct 10 '22 16:10 8go

Ok the bot.db directory is there but is empty. If I try to write to the directory as a regular user there is a permission error, but using sudo is ok. I run docker as sudo so I would think it should be ok, unless something was supposed to be written there during the docker first run. I can prune all the docker stuff and re-run and see what happens.

minnixtx avatar Oct 10 '22 19:10 minnixtx

bot.db is not a directory, should not be a directory. bot.db is a file to be created.

database_filepath: "/foo/bar/bot.db" then /foo/bar must be an existing dir with write permissions.

database_filepath: "bot.db" then . must be an existing dir with write permissions. . obviously depends, where you are, from where you execute the command, ...

8go avatar Oct 10 '22 19:10 8go

So I removed the bot.db directory that was created from docker compose and just created a blank bot.db file to see what would happen than ran docker compose up again. This is the error:

[+] Running 1/0
 ⠿ Container matrix-eno-bot  Created                                                                                                                                                                                                   0.0s
Attaching to matrix-eno-bot
Error response from daemon: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: error during container init: error mounting "/home/minnix/matrix-eno-bot/bot.db" to rootfs at "/bot/bot.db": mount /home/minnix/matrix-eno-bot/bot.db:/bot/bot.db (via /proc/self/fd/6), flags: 0x5000: not a directory: unknown: Are you trying to mount a directory onto a file (or vice-versa)? Check if the specified host path exists and is the expected type

So it looks like it is expecting bot.db to be a directory

minnixtx avatar Oct 11 '22 04:10 minnixtx

bot.db as directory: definitely wrong bot.db as a blank file: definitely wrong

Your Docker file is not correct. If done correctly, the eno bot creates the bot.db file.

8go avatar Oct 11 '22 08:10 8go

Dockerfile

FROM python:3.7-slim

RUN apt update && apt upgrade -y && \
    apt install -y \
        wget \
        libmagic1 \
        build-essential

WORKDIR /bot

COPY *.py /bot/
COPY *.yaml /bot/
COPY *.txt /bot/
COPY eno /bot/eno/

# download libolm3 from Ubuntu focal distribution
# https://packages.ubuntu.com/focal/libolm3
# https://packages.ubuntu.com/focal/libolm-dev
RUN wget http://mirrors.kernel.org/ubuntu/pool/universe/o/olm/libolm-dev_3.1.3+dfsg-2build2_amd64.deb
RUN wget http://mirrors.kernel.org/ubuntu/pool/universe/o/olm/libolm3_3.1.3+dfsg-2build2_amd64.deb
RUN dpkg -i ./*.deb
RUN pip install -r requirements.txt

# clean up apt cache and remove gcc
RUN apt purge -y build-essential && \
    apt autoremove -y && apt clean && \
    rm -rf /var/lib/apt/lists/*

CMD [ "python", "./main.py" ]
docker-compose.yaml

version: '3'

services:
  matrix-eno-bot:
    container_name: matrix-eno-bot
    image: 'matrix-eno-bot:latest'
    build: '.'
    restart: always
    volumes:
      - /home/minnix/matrix-eno-bot/config.yaml:/bot/config.yaml
      - /home/minnix/matrix-eno-bot/commands.yaml:/bot/commands.yaml
      - /home/minnix/matrix-eno-bot/bot.db:/bot/bot.db
      - /home/minnix/matrix-eno-bot/store/:/bot/store/
    stop_signal: SIGINT

If there's anything amiss you can find please let me know.

minnixtx avatar Oct 13 '22 05:10 minnixtx

I am not expert enough on Docker to make comments on Docker files.

Run it without Docker. You will see, that if bot.db is a directory, it will fail. If bot.db is an empty file it will also fail. And if bot.db does not exist it will create it. And if bot.db is a valid Sqlite file it will read it and use it.

8go avatar Oct 14 '22 11:10 8go

Ok, trying to run it without docker. I'm having problems understanding what the paths should be in the systemctl file.

[Unit]
Description=matrix-eno-bot

[Service]
# change user name to fit your needs
User=matrix-neo-bot
Group=users
Environment=PYTHONUNBUFFERED=1
# change this to match your server's timezone
Environment=TZ=UTC
# change this PATH to fit your needs
Environment=PATH=/home/matrix-eno-bot/matrix-eno-bot/eno/scripts:/home/matrix-eno-bot/bin:/home/matrix-eno-bot/.local/bin:/home/matrix-eno-bot/Scripts:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# change this PATH to fit your PATH
ExecStart=/home/matrix-eno-bot/matrix-eno-bot/main.py /home/matrix-eno-bot/matrix-eno-bot/config.yaml
ExecStop=/bin/kill -9 $MAINPID
Restart=on-failure
RestartSec=30

[Install]
WantedBy=multi-user.target

the matrix-eno-bot folder is in my user folder, /home/minnix/. how should Environment=PATH= and ExecStart= read? Also do I need to create a User called matrix-neo-bot?

minnixtx avatar Oct 16 '22 05:10 minnixtx

To start with you do not need any of this, this is if and only if you want to run it as a service. But you can run it without a service as well, just starting it by hand.

Environment=PATH= should include paths to all the scripts and binaries that your bot is going to call.

ExecStart= just has a full path to program and to 1 argument.

In your case likely: ExecStart=/home/minnix/matrix-eno-bot/main.py /home/minnix/matrix-eno-bot/config.yaml

You should be able to run this is your terminal /home/minnix/matrix-eno-bot/main.py /home/minnix/matrix-eno-bot/config.yaml

User called matrix-neo-bot? No, not needed.

8go avatar Oct 16 '22 09:10 8go