Snap Impression (by quqi99)

版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本版权声明 (http://blog.csdn.net/quqi99)

Snap一是把应用隔离在沙盒之内,保证不同应用之间或者应用不同版本之间完全在文件系统上进行隔离,这样就彻底解决应用之间升级互相影响的问题(依赖全部打包,并且通过mount namespace与发行版的文件隔离, 所以snap应用可以安装同样一个软件的不同版本);二是通过引入应用程序运行时的runtime接口,保证了系统和应用之间有一个稳定的接口,从侧面也保证了系统和应用之间的依赖分离。理论上一个snap应用可以安装到任何一个Linux的发行版上,因为它不依赖于操作系统及其发布版本。
Classic Ubuntu基于deb打包, Ubuntu Core(过去叫snappy)基于snap打包. snapcraft是一套snap打包工具集.

Basic Usage

#Snap Basic Usage
sudo apt install snapd
snap version
snap login
snap whoami
snap find vlc #all snaps - https://uappexplorer.com/apps
snap info vlc
sudo snap install vlc --channel=edge   #risk-levels: stable, candidate, beta edge
sudo snap switch --channel=stable vlc
sudo snap revert vlc
snap list |grep vlc
sudo snap disable vlc
sudo snap enable vlc
sudo snap refresh --channel=beta

#service management
sudo snap services lxd
sudo snap start|stop|restart lxd
sudo snap logs lxd -f

#interface management - https://docs.snapcraft.io/t/interface-management/6154
snap interfaces vlc
ll /var/lib/snapd/snaps/vlc_555.snap
sudo unsquashfs -l /var/lib/snapd/snaps/vlc_555.snap |less
$ mount |grep vlc
/var/lib/snapd/snaps/vlc_555.snap on /snap/vlc/555 type squashfs (ro,nodev,relatime,x-gdu.hide)

# public snap
snapcraft login
snapcraft list-registered
snapcraft register awesome-database
snapcraft push awesome-databse_0.1_amd64.snap --release edge
#(devmode) cannot target a stable channel (stable, grade: devel)
snapcraft release awesome-database 1 stable
snapcraft status awesome-database
snapcraft history awesome-database

#In order to enrich logs with k8s metadata in cdk (https://github.com/charmed-kubernetes/bundle/issues/651), must first upgrade filebeat to 6.x (https://github.com/juju-solutions/layer-filebeat/pull/66), but create a new snap track instead (https://bugs.launchpad.net/graylog-snap/+bug/1824708)
#deploy the charm using: 
juju deploy cs:graylog-39 
#and then change it to v3 using
snap info graylog
juju config graylog channel=3/stable 
#or deploy it to use v3 directly: 
juju deploy cs:graylog-39 --config channel=3/stable 
juju deploy cs:graylog-39 --config channel=3/stable --series xenial graylog2
charm pull cs:~graylog-charmers/graylog-39
snap install --channel=3/stable graylog

Where can Ubuntu snaps write data?

https://askubuntu.com/questions/762354/where-can-ubuntu-snaps-write-data
Long story short: don’t use the home interface to store data you can be storing in SNAP_DATA or SNAP_USER_DATA, since they’re an integral part of the upgrade/rollback strategy. Take advantage of them!
UPDATE for v2.0.10:
Two new data directories were also introduced:

  • SNAP_COMMON sits alongside SNAP_DATA, but is specifically unversioned. Every revision of the specific snap has access to this directory, so it’s not copied upon upgrade/rollback etc. This might be used for particularly large, unversioned files (e.g. raw data that isn’t really version-specific).
    • SNAP_USER_COMMON sits alongside SNAP_USER_DATA, but is again specifically unversioned. It might be used for storing non-version-specific data per user.
hua@t440p:~$ sudo snap run --shell chinadns
root@t440p:/home/hua# env |grep SNAP
SNAP_USER_COMMON=/root/snap/chinadns/common
SNAP_CONTEXT=S5CbWj89PfpclqCBDK475t5Qfrr1T3WmX4Qrc4skktgw
SNAP_REEXEC=
SNAP_LIBRARY_PATH=/var/lib/snapd/lib/gl:/var/lib/snapd/lib/gl32:/var/lib/snapd/void
SNAP_COMMON=/var/snap/chinadns/common
SNAP_INSTANCE_NAME=chinadns
SNAP_USER_DATA=/root/snap/chinadns/1
SNAP_DATA=/var/snap/chinadns/1
SNAP_REVISION=1
SNAP_NAME=chinadns
SNAP_COOKIE=S5CbWj89PfpclqCBDK475t5Qfrr1T3WmX4Qrc4skktgw
SNAP_ARCH=amd64
SNAP_VERSION=20181214
SNAP=/snap/chinadns/1
SNAP_INSTANCE_KEY=

# according to https://docs.ubuntu.com/core/en/guides/intro/security
HOME: set to SNAP_USER_DATA for all commands
SNAP: read-only install directory
SNAP_ARCH: the architecture of device (eg, amd64, arm64, armhf, i386, etc)
SNAP_DATA: writable area for a particular revision of the snap
SNAP_COMMON: writable area common across all revisions of the snap
SNAP_LIBRARY_PATH: additional directories which should be added to LD_LIBRARY_PATH
SNAP_NAME: snap name
SNAP_REVISION: store revision of the snap
SNAP_USER_DATA: per-user writable area for a particular revision of the snap
SNAP_USER_COMMON: per-user writable area common across all revisions of the snap
SNAP_VERSION: snap version (from snap.yaml)

在这里插入图片描述

Build Your Own Snap

https://tutorials.ubuntu.com/tutorial/create-your-first-snap

sudo apt install build-essential
sudo snap install --classic snapcraft
mkdir hello && cd hello
snapcraft init
sudo bash -c 'cat >snap/snapcraft.yaml' <<EOF
name: hello # you probably want to 'snapcraft register <name>'
version: '0.1' # just for humans, typically '1.2+git' or '1.3.2'
summary: Single-line elevator pitch for your amazing snap # 79 char long summary
description: |
  This is my-snap's description.
grade: devel # must be 'stable' to release into candidate/stable channels
confinement: devmode # use 'strict' once you have the right plugs and slots
apps:
  hello:
    command: bin/hello
parts:
  my-part:
    source: http://ftp.gnu.org/gnu/hello/hello-2.10.tar.gz
    plugin: autotools
EOF
snapcraft
#snapcraft prime  #omitting the "pack" step
#sudo snap try --devmode prime
sudo snap install ./hello_0.1_amd64.snap --dangerous --devmode
snap list hello

Try snap timg exmaple

https://blog.simos.info/how-to-create-a-snap-for-timg-with-snapcraft-on-ubuntu/

#proxychains git clone https://github.com/hzeller/timg
mkdir timg && cd timg
snapcraft init
snapcraft register timg
snapcraft --help
snapcraft list-plugins
sudo apt-cache search graphicsmagick++ | grep dev
ls ./parts/timg/build/src/

sudo bash -c 'cat >snap/snapcraft.yaml' <<EOF
name: timg
version: 20181214 
summary: A terminal image viewer
description: |
  A viewer that uses 24-Bit color capabilities and unicode
  character blocks to display images in the terminal.
#grade: devel 
#confinement: devmode
#confinement: classic #timg snap have read-access to all the filesystem
grade: stable 
confinement: strict
apps:
  timg:
    command: timg
    plugs: [home]
parts:
  timg:
    source: https://github.com/hzeller/timg.git
    source-subdir: src
    plugin: make
    build-packages:
      - libgraphicsmagick++1-dev
      - libwebp-dev
    artifacts: [timg]
EOF

#snapcraft clean timg -s pull
snapcraft prime
#snap try --jailmode prime
snap try --devmode prime
snap list

snapcraft clean
snapcraft
lxc launch ubuntu:x snaptesting
lxc file push timg_20181214_amd64.snap snaptesting/home/ubuntu/
lxc exec snaptesting -- sudo su - ubuntu
ubuntu@snaptesting:~$ sudo apt update
ubuntu@snaptesting:~$ sudo apt install squashfuse
ubuntu@snaptesting:~$ sudo snap install timg_20181214_amd64.snap --dangerous

# pushed the .snap file to the Ubuntu Store and we got a revision number 6
snapcraft push timg_20181214_amd64.snap
# Then, we released the timg revision 6 to the stable channel of the Ubuntu Store
# There was no released snap in the candidate channel, therefore, it inherits the package from the stable channel
snapcraft release timg 6 stable

# In previous tests I uploaded some older versions of the snap to the beta and edge channels. These older versions refer to the old tag 0.9.5 of the timg source code.
# Let’s knock down the old 0.9.5 by releasing the stable version to the beta and edge channels as well.
snapcraft release timg 6 beta
snapcraft release timg 6 edge

Snap pdnsd

# http://members.home.nl/p.a.rombouts/pdnsd/
# https://github.com/shadowsocks/pdnsd
snapcraft init
sudo bash -c 'cat >snap/snapcraft.yaml' <<EOF
name: mypdnsd
version: '1.2.9a-par'
summary: pdnsd is a proxy DNS server
description: |
  pdnsd is a proxy DNS server with permanent caching that is designed to
  cope with unreachable or down DNS servers.
  see - http://members.home.nl/p.a.rombouts/pdnsd/
grade: stable 
confinement: strict
#grade: devel
#confinement: devmode
apps:
  pdnsd:
    command: sbin/pdnsd -c /var/snap/pdnsd/x1/etc/pdnsd.conf
    plugs: [network-bind]
parts:
  pdnsd:
    source: http://members.home.nl/p.a.rombouts/pdnsd/releases/pdnsd-1.2.9a-par.tar.gz
    plugin: autotools
EOF
snapcraft clean
snapcraft
sudo snap install ./pdnsd_1.2.9a-par_amd64.snap --dangerous
#sudo snap install ./pdnsd_1.2.9a-par_amd64.snap --dangerous --devmode
sudo mkdir -p /var/snap/pdnsd/x1/etc
sudo mkdir -p /var/snap/pdnsd/common/var/cache/pdnsd
sudo bash -c 'cat >/var/snap/pdnsd/x1/etc/pdnsd.conf' <<EOF
global {
        debug=on;
	perm_cache=4096;               //cache size,KB
	cache_dir="/var/snap/pdnsd/common/var/cache/pdnsd";  //cache file position
	#run_as="pdnsd";                //just leave default
	server_ip = "eth0";            // Use your interface name, or your IP address or 0.0.0.0
	server_port = 5354;            //bind port, just do not use port 53
        status_ctl=on;                 # pdnsd-ctl dump && pdnsd-ctl empty-cache
  	paranoid=on;
	query_method=tcp_only;     
	min_ttl=1d;    
	max_ttl=1w;	  
	neg_ttl=120s;
	timeout=5;
}
server {
	label = "mydns";
 	ip = 114.114.114.114,8.8.8.8;
        #ip="2001:4860:4860::8888","2001:4860:4860::8844";
	timeout = 3;
	uptest = none;
	purge_cache = off;
	edns_query = on;
	exclude = .localdomain;
}
EOF
pdnsd -c /var/snap/pdnsd/x1/etc/pdnsd.conf
dig baidu.com @192.168.99.135 -p 5354
snapcraft register mypdnsd
snapcraft push mypdnsd_1.2.9a-par_amd64.snap
#snapcraft release mypdnsd 1 stable
snapcraft release mypdnsd 1 beta
snapcraft release mypdnsd 1 edge
sudo snap install mypdnsd --channel=beta --devmode

example - webcam-webui

# https://blog.csdn.net/ubuntutouch/article/details/49864117
$ cat snap/snapcraft.yaml 
name: webcam-webui
version: '0.1'
summary: Webcam Web UI
description: |
  Expose your webcam over a Web UI
#icon: icon.png
grade: devel # must be 'stable' to release into candidate/stable channels
confinement: devmode # use 'strict' once you have the right plugs and slots
apps:
  webcam-webui:
    command: bin/webcam-webu
parts:
  cam:
    plugin: go
    source: git://github.com/mikix/golang-static-http
    stage-packages:
      - fswebcam
    filesets:
      fswebcam:
        - usr/bin/fswebcam
        - lib
        - usr/lib
      go-server:
        - bin/golang-*
    snap:
      - $fswebcam
      - $go-server
  glue:
    plugin: copy
    files:
      webcam-webui: bin/webcam-webui

example - writable config file and service

$ cat snap/snapcraft.yaml 
name: my-snap-name
version: '0.1' 
summary: the example about writeable config and daemon 
description: hellod
grade: devel 
confinement: strict
hooks:
  install:
    plugs: [network]
parts:
  my-service:
    plugin: dump
    source: src/
apps:
  hellod:
    command: hellod
    daemon: simple

$ cat src/bin/hellod 
#!/bin/sh -e
config_file="$SNAP_DATA/hello.conf"
while true; do
    # First, determine our rate by determining how long we should sleep
    sleep_time="$(awk '/^sleep_time/{print $2}' "$config_file")"
    # Now be nice and greet
    echo "Hello, World!"
    # Now sleep for the time specified in the config file
    sleep "$sleep_time"
done

$ cat snap/hooks/install 
#!/bin/sh -e
# Create a default config file
echo "sleep_time 5" > "$SNAP_DATA/hello.conf"

snapcraft
sudo snap install ./my-snap-name_0.1_amd64.snap --dangerous

$ snap changes
ID   Status  Spawn               Ready               Summary
8    Done    today at 15:49 UTC  today at 15:49 UTC  Remove "hellod" snap
9    Done    today at 15:49 UTC  today at 15:49 UTC  Install "my-snap-name" snap from file "./my-snap-name_0.1_amd64.snap"

$ snap change 9
Status  Spawn               Ready               Summary
Done    today at 15:49 UTC  today at 15:49 UTC  Ensure prerequisites for "my-snap-name" are available
Done    today at 15:49 UTC  today at 15:49 UTC  Prepare snap "/var/lib/snapd/snaps/.local-install-180914816" (unset)
Done    today at 15:49 UTC  today at 15:49 UTC  Mount snap "my-snap-name" (unset)
Done    today at 15:49 UTC  today at 15:49 UTC  Copy snap "my-snap-name" data
Done    today at 15:49 UTC  today at 15:49 UTC  Setup snap "my-snap-name" (unset) security profiles
Done    today at 15:49 UTC  today at 15:49 UTC  Make snap "my-snap-name" (unset) available to the system
Done    today at 15:49 UTC  today at 15:49 UTC  Automatically connect eligible plugs and slots of snap "my-snap-name"
Done    today at 15:49 UTC  today at 15:49 UTC  Set automatic aliases for snap "my-snap-name"
Done    today at 15:49 UTC  today at 15:49 UTC  Setup snap "my-snap-name" aliases
Done    today at 15:49 UTC  today at 15:49 UTC  Run install hook of "my-snap-name" snap if present
Done    today at 15:49 UTC  today at 15:49 UTC  Start snap "my-snap-name" (unset) services
Done    today at 15:49 UTC  today at 15:49 UTC  Run configure hook of "my-snap-name" snap if present
Done    today at 15:49 UTC  today at 15:49 UTC  Connect my-snap-name:network to core:network

$ journalctl -fu snap.my-snap-name.hellod.service 
-- Logs begin at Wed 2018-12-05 03:12:06 UTC. --
Dec 14 15:49:44 vpn-jpc systemd[1]: Started Service for snap application my-snap-name.hellod.
Dec 14 15:49:44 vpn-jpc my-snap-name.hellod[12918]: Hello, World!

can also refer this example:

# https://docs.ubuntu.com/core/en/guides/build-device/config-hooks
1, when the user run: juju config kubernetes-master enable-metrics=true
2, kubernetes-master charm will run: snap set cdk-addons enable-metrics=true
3, meta/hooks/configure in cdk-addons snap will run: snapctl get enable-metrics > "$SNAP_DATA/config/enable-metrics"
4, then kubernetes-master charm call: cdk-addons.apply
   cdk-addons is a simple service snap.
grade: stable
confinement: strict
apps:
  apply:
    command: apply
    plugs:
    - home
    - network
parts:
  cdk-addons:
    plugin: dump
    source: .
5, cdk-addons.apply will 
    if get_snap_config("enable-metrics", required=False) == "true":
        ...
        render_template("resource-reader.yaml", context)
        rendered = True
6, when running make to generate cdk-addons snap, the following two lines in get_addon_templates will be invokded to generate the initial template in the cdk re-install dir
        # if the upstream pr doens't be approved (eg: https://github.com/kubernetes/kubernetes/pull/72235), we can patch here
        patch_metrics_reader(repo, "metrics-server/resource-reader.yaml")
        add_addon(repo, "metrics-server/resource-reader.yaml", dest)
upstream version: https://github.com/kubernetes-incubator/metrics-server/blob/master/deploy/1.8%2B/resource-reader.yaml 
cdk version: https://github.com/juju-solutions/kubernetes/blob/master/cluster/addons/metrics-server/resource-reader.yaml 
cdk re-install dir: /snap/cdk-addons/current/templates/resource-reader.yaml

Code

git clone https://github.com/snapcore/snapd.git

Use snap to install k8s

https://kyrofa.com/posts/snap-install-time-setup-the-install-hook
sudo snap install microk8s --beta --classic
microk8s.kubectl get no
microk8s.enable dns dashboard
microk8s.kubectl get all --all-namespaces
lynx http://xxxx

Use snap to install openstack

git clone https://github.com/openstack-snaps/snap-test
cd snap-test
sudo -i
./snap-deploy

Debug NOTE:

# NOTE: need to delete bin files (eg: /usr/local/bin/keystone) so that to use /snap/bin/keystone
sudo snap remove keystone
sudo snap install --channel=ocata/edge keystone
mount |grep keystone
sudo unsquashfs -l /var/lib/snapd/snaps/keystone_163.snap |less

Rest openstack env

rm -rf ~/.cache/pip/*
sudo chown -R $USER  ~/.cache/pip/
#find . -name "*.pyc" -exec rm -rf {} \;
sudo rm -rf /usr/local/lib/python2.7/dist-packages/oslo*
sudo rm -rf /usr/local/lib/python2.7/dist-packages/*keystone*
sudo rm -rf /usr/local/lib/python2.7/dist-packages/*glance*
sudo rm -rf /usr/local/lib/python2.7/dist-packages/*swift*
sudo rm -rf /usr/local/lib/python2.7/dist-packages/*cinder*
sudo rm -rf /usr/local/lib/python2.7/dist-packages/*nova*
sudo rm -rf /usr/local/lib/python2.7/dist-packages/*neutron*
sudo rm -rf /usr/local/bin/keystone*
sudo rm -rf /usr/local/bin/glance*
sudo rm -rf /usr/local/bin/nova*
sudo rm -rf /usr/local/bin/neutron*
sudo rm -rf /usr/local/bin/cinder*

sudo apt install python-setuptools python-dev libpython-dev libssl-dev libmysqlclient-dev libxml2-dev libxslt-dev libxslt1-dev libpq-dev git git-review libffi-dev gettext graphviz libjpeg-dev zlib1g-dev build-essential python-nose python-mock python3-dev python3-nose python3-mock python3.6 python3.6-dev libssl1.1 python-virtualenv
sudo apt remove --purge python-pip python3-pip
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py |sudo python

sudo pip install -c https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=stable/ocata .
sudo pip install -c https://raw.githubusercontent.com/openstack/keystone/stable/ocata/requirements.txt .
sudo pip install -c https://raw.githubusercontent.com/openstack/keystone/stable/ocata/test-requirements.txt .

附录 - k8s snap

https://github.com/juju-solutions/release/tree/rye/snaps/snap/shared
https://github.com/juju-solutions/bundle-canonical-kubernetes/wiki/Kubernetes-Snaps:-The-Quick-Version
sudo snap set kube-apiserver client-ca-file=/root/cdk/ca.crt
sudo snap set kube-apiserver requestheader-client-ca-file=/root/cdk/ca.crt
sudo service snap.kube-apiserver.daemon restart
sudo systemctl status snap.kube-apiserver.daemon
kubectl describe --namespace=kube-system configmap extension-apiserver-authentication

debug snap maas

更多的snap maas debug,见:https://zhhuabj.blog.csdn.net/article/details/81173442

sudo unsquashfs -d squashfs-root /var/lib/snapd/snaps/maas-*.snap
sudo unsquashfs -d maas-cli /var/lib/snapd/snaps/maas-cli_13.snap
sudo snap remove maas
sudo snap remove maas-cli
sudo snap try ./squashfs-root/ --devmode
sudo snap try ./maas-cli/ --devmode

# using existing maasdb
sudo /snap/bin/maas init region+rack --database-uri "postgres://maas:password@localhost/maasdb"

# it will change /snap/maas/current/usr/lib/python3/dist-packages/django/db/backends/postgresql/base.py
vim ./squashfs-root/usr/lib/python3/dist-packages/django/db/backends/postgresql/base.py#get_new_connection
import rpdb;rpdb.set_trace()
# install rpdb to squashfs-root/lib/python3.8/site-packages/rpdb/
sudo ./squashfs-root/bin/python3 -m pip install rpdb

sudo service snap.maas.supervisor restart
./squashfs-root/bin/python3 /tmp/test.py aa
nc 127.0.0.1 4444

20231016更新 - 可以试试下面的方法:

cp /snap/maas/current/lib/python3.10/site-packages/maasserver/preseed.py .
sudo mount --bind preseed.py /snap/maas/current/lib/python3.10/site-packages/maasserver/preseed.py
vi /snap/maas/current/lib/python3.10/site-packages/maasserver/preseed.py
sudo systemctl restart snap.maas.supervisor

cp /snap/openstack/274/lib/python3.10/site-packages/sunbeam/utils.py .
sudo mount --bind utils.py /snap/openstack/274/lib/python3.10/site-packages/sunbeam/utils.py 
mount | grep "utils.py"
#now we can modify /snap/openstack/274/lib/python3.10/site-packages/sunbeam/utils.py  (NOTE: it's not ./utils.py)
LOG.warn("quqi {}".format(nameservers[:max_count]))
#python needs to be restarted if they are running in the daemon
sudo systemctl restart snap.openstack.clusterd.service
sunbeam configure --accept-defaults --openrc demo-openrc #trigger it

#但是想要mount --bind一个目录然后使用rpdb,没测成功(Terraform lock问题导致环境无法继续用了,改天再确认)
mkdir -p ~/snap_write/ && cp -r /snap/openstack/274 ~/snap_write/
sudo mount --bind ~/snap_write/274 /snap/openstack/274
mount |grep /snap/openstack/274
vim /snap/openstack/274/lib/python3.10/site-packages/sunbeam/utils.py
LOG.warn("quqi {}".format(nameservers[:max_count]))
import rpdb;rpdb.set_trace()
/snap/openstack/274/bin/pip install rpdb
#python needs to be restarted if they are running in the daemon
sudo systemctl restart snap.openstack.clusterd.service
sunbeam configure --accept-defaults --openrc demo-openrc #trigger it
nc 127.0.0.1 4444

20220307 - snap maas与debian maas的区别

在debian maas中修改database-host的方法如下:

maas-region local_config_set --database-host=10.5.150.115
maas-region local_config_get --database-host

而在snap maas中可以通过下列方法修改, 因为/snap/maas/8724/bin/maas-region将调用/snap/maas/8724/lib/python3.6/site-packages/maasserver/region_script.py来为django设置snap的环境(DJANGO_SETTINGS_MODULE)

snap run --shell maas
/snap/maas/8724/bin/maas-region local_config_set --database-host=10.5.150.115
/snap/maas/8724/bin/maas-region local_config_get --database-host

但是对于bionic即使做上面的设置也是不行的.因为bionic没有直接使用deb,也没有直接使用snap, 而是默认通过snap-transition这个deb包来包装使用snap.在snap-transition/debian/maas.preinst会先删除maas snap再重新安装maas snap,所以上面的database-host在新安装的snap maas中仍然会消失.可能需要重新修改snap-transition在重新安装snap maas后马上运行上面命令吧,但也不容易,因为上面的命令似乎不能在一行中执行:

# snap run --shell maas./snap/maas/8724/bin/maas-region local_config_get --database-host
error: cannot find app "/snap/maas/8724/bin/maas-region" in "maas"

并且以前处理debian出差的绝招好像也用不上, 因为snap-transition未装成功/var/lib/dpkg/info/maas.preinst就还不存在:

sudo rm -rf /var/lib/dpkg/info/maas.preinst
sudo apt remove maas-rack-controller --purge

Reference

[1] https://snapcraft.io/docs/core/
[2] https://snapcraft.io/docs/build-snaps/
[3] http://www.ubuntukylin.com/news/shownews.php?lang=cn&id=666
[4] https://www.linuxidc.com/Linux/2016-08/134676p2.htm

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

quqi99

你的鼓励就是我创造的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值