feat: 给update.sh里的 dns 配上环境变量
This commit is contained in:
commit
105333dd6f
71
.github/workflows/docker-publish.yml
vendored
Normal file
71
.github/workflows/docker-publish.yml
vendored
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
name: Docker
|
||||||
|
|
||||||
|
# This workflow uses actions that are not certified by GitHub.
|
||||||
|
# They are provided by a third-party and are governed by
|
||||||
|
# separate terms of service, privacy policy, and support
|
||||||
|
# documentation.
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '40 23 * * *'
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
# Publish semver tags as releases.
|
||||||
|
tags: [ 'v*.*.*' ]
|
||||||
|
# pull_request:
|
||||||
|
# branches: [ main ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
# Use docker.io for Docker Hub if empty
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
# github.repository as <account>/<repo>
|
||||||
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
# This is used to complete the identity challenge
|
||||||
|
# with sigstore/fulcio when running outside of PRs.
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
# Workaround: https://github.com/docker/build-push-action/issues/461
|
||||||
|
- name: Setup Docker buildx
|
||||||
|
uses: docker/setup-buildx-action@79abd3f86f79a9d68a23c75a09a9a85889262adf
|
||||||
|
|
||||||
|
# Login against a Docker registry except on PR
|
||||||
|
# https://github.com/docker/login-action
|
||||||
|
- name: Log into registry
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
# Extract metadata (tags, labels) for Docker
|
||||||
|
# https://github.com/docker/metadata-action
|
||||||
|
- name: Extract Docker metadata
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
|
||||||
|
# Build and push Docker image with Buildx (don't push on PR)
|
||||||
|
# https://github.com/docker/build-push-action
|
||||||
|
- name: Build and push Docker image
|
||||||
|
id: build-and-push
|
||||||
|
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
45
.github/workflows/update-cert.yml
vendored
Normal file
45
.github/workflows/update-cert.yml
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
name: Update Cert
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 10 1 * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Prepare Environment
|
||||||
|
run: |
|
||||||
|
curl https://get.acme.sh | sh -s email=my@example.com
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
- name: Update Cert
|
||||||
|
env:
|
||||||
|
DP_Id: ${{ secrets.DP_Id }}
|
||||||
|
DP_Key: ${{ secrets.DP_Key }}
|
||||||
|
ACME_DNS_TYPE: ${{ secrets.ACME_DNS_TYPE }}
|
||||||
|
ACME_DOMAIN: ${{ secrets.ACME_DOMAIN }}
|
||||||
|
SECRETID: ${{ secrets.SECRETID }}
|
||||||
|
SECRETKEY: ${{ secrets.SECRETKEY }}
|
||||||
|
CDN_DOMAIN: ${{ secrets.CDN_DOMAIN }}
|
||||||
|
CERT_HOME: /home/runner/.acme.sh
|
||||||
|
ACME_HOME: /home/runner/.acme.sh
|
||||||
|
WORK_DIR: .
|
||||||
|
run: sh ./docker/update.sh
|
||||||
|
|
||||||
|
- name: Notification
|
||||||
|
uses: monlor/bark-action@v3
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.BARK_HOST}} # not required
|
||||||
|
key: ${{ secrets.BARK_KEY }} # Your secret key
|
||||||
|
title: Github Actions
|
||||||
|
body: 'Your tencent cdn certs update ${{ job.status }}!'
|
||||||
|
isArchive: 1
|
||||||
|
url: 'github://github.com/${{ github.repository }}'
|
||||||
|
group: Github
|
||||||
|
icon: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png
|
||||||
|
copy: ${{ steps.meta.outputs.tags }}
|
129
.gitignore
vendored
Normal file
129
.gitignore
vendored
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
pip-wheel-metadata/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
5
.idea/.gitignore
vendored
Normal file
5
.idea/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
5
.idea/inspectionProfiles/Project_Default.xml
Normal file
5
.idea/inspectionProfiles/Project_Default.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
</profile>
|
||||||
|
</component>
|
6
.idea/jsLibraryMappings.xml
Normal file
6
.idea/jsLibraryMappings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="JavaScriptLibraryMappings">
|
||||||
|
<includedPredefinedLibrary name="Node.js Core" />
|
||||||
|
</component>
|
||||||
|
</project>
|
6
.idea/misc.xml
Normal file
6
.idea/misc.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="JavaScriptSettings">
|
||||||
|
<option name="languageLevel" value="ES6" />
|
||||||
|
</component>
|
||||||
|
</project>
|
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/qcloud-ssl-cdn.iml" filepath="$PROJECT_DIR$/.idea/qcloud-ssl-cdn.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
12
.idea/qcloud-ssl-cdn.iml
Normal file
12
.idea/qcloud-ssl-cdn.iml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
27
Dockerfile
Normal file
27
Dockerfile
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
FROM python:alpine
|
||||||
|
|
||||||
|
LABEL AUTHOR="monlor"
|
||||||
|
|
||||||
|
ENV LANG="C.UTF-8"
|
||||||
|
ENV TZ="Asia/Shanghai"
|
||||||
|
ENV CERT_HOME="/data/certs"
|
||||||
|
ENV WORK_DIR="/data"
|
||||||
|
ENV ACME_HOME="/root/.acme.sh"
|
||||||
|
|
||||||
|
WORKDIR /data
|
||||||
|
|
||||||
|
COPY api /data/api
|
||||||
|
COPY main.py /data/
|
||||||
|
COPY requirements.txt /data/
|
||||||
|
COPY docker/entrypoint.sh /
|
||||||
|
COPY docker/update.sh /data
|
||||||
|
|
||||||
|
RUN apk update && apk add --no-cache curl openssl socat && \
|
||||||
|
ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime && \
|
||||||
|
echo "${TZ}" > /etc/timezone && \
|
||||||
|
pip install -r requirements.txt && \
|
||||||
|
chmod +x /entrypoint.sh /data/update.sh && \
|
||||||
|
curl https://get.acme.sh | sh -s email=my@example.com
|
||||||
|
|
||||||
|
VOLUME ["/data/certs"]
|
||||||
|
ENTRYPOINT [ "/entrypoint.sh" ]
|
201
LICENSE
Normal file
201
LICENSE
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
94
README.md
Normal file
94
README.md
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
## 腾讯云自动SSL证书上传及替换
|
||||||
|
功能:
|
||||||
|
* 把本地的SSL证书上传到腾讯云[SSL证书](https://console.cloud.tencent.com/ssl),并记录id
|
||||||
|
* 为CDN服务更换指定id的SSL证书
|
||||||
|
* 根据网址,批量预热URL
|
||||||
|
|
||||||
|
目的:
|
||||||
|
* 把利用[acme.sh](https://github.com/acmesh-official/acme.sh)申请的`Let's Encrypt`证书上传到腾讯云
|
||||||
|
* 由于多次申请`TrustAsia`的一年期免费单域名证书失败,所以准备使用`Let's Encrypt`证书
|
||||||
|
* 该程序已将每一个步骤都实现:自动上传SSL并替换CDN的证书
|
||||||
|
* 为了使网站访问更快,每天预热URL(可以单独抽出该函数,运行在[腾讯云函数](https://github.com/zfb132/auto_push_url))
|
||||||
|
|
||||||
|
|
||||||
|
## 部署方式
|
||||||
|
|
||||||
|
### 使用 Docker 快速部署
|
||||||
|
|
||||||
|
每月 1 号凌晨 2 点定时执行证书更新
|
||||||
|
|
||||||
|
* `ACME_DNS_TYPE`: Acme 的 dns 类型,你可以选择你的 dns 类型并配置[环境变量密钥](https://github.com/acmesh-official/acme.sh/wiki/dnsapi)
|
||||||
|
* `ACME_DOMAIN`: 你的顶级域名,例如:monlor.com,自动申请证书 monlor.com/*.monlor.com
|
||||||
|
* `SECRETID`: 腾讯云 Secret Id
|
||||||
|
* `SECRETKEY`: 腾讯云 Secret Key
|
||||||
|
* `CDN_DOMAIN`: CDN 域名,多个域名用逗号分隔
|
||||||
|
* `RUN_NOW`: 是否在 Docker 启动时执行程序
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d \
|
||||||
|
--name qcloud-ssl-cdn \
|
||||||
|
--restart=unless-stopped \
|
||||||
|
-e DP_Id=xxx \
|
||||||
|
-e DP_Key=xxx \
|
||||||
|
-e ACME_DNS_TYPE=dns_dp \
|
||||||
|
-e ACME_DOMAIN=monlor.com \
|
||||||
|
-e SECRETID=xxx \
|
||||||
|
-e SECRETKEY=xxx \
|
||||||
|
-e CDN_DOMAIN=www.monlor.com \
|
||||||
|
-e RUN_NOW=true \
|
||||||
|
ghcr.io/monlor/qcloud-ssl-cdn:main
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 其他变量
|
||||||
|
|
||||||
|
* `ACME_ENABLED`: 是否启用 acme,不启用将证书映射到容器`/data/certs`目录
|
||||||
|
* `PUSH_URLS`: CDN 刷新/预热地址,逗号分隔
|
||||||
|
* `PUSH_URLS_PATH`: CDN 刷新/预热地址文件路径,文件映射到 Docker 容器,**路径不能是`/data/urls.txt`**
|
||||||
|
|
||||||
|
### 使用 GitHub Action 部署
|
||||||
|
|
||||||
|
Fork 此项目,配置以下 Github Action Secrets
|
||||||
|
|
||||||
|
* `ACME_DNS_TYPE`: Acme 的 dns 类型,你可以选择你的 dns 类型并配置[环境变量密钥](https://github.com/acmesh-official/acme.sh/wiki/dnsapi)
|
||||||
|
* `ACME_DOMAIN`: 你的顶级域名,例如:monlor.com,自动申请证书 monlor.com/*.monlor.com
|
||||||
|
* `SECRETID`: 腾讯云 Secret Id
|
||||||
|
* `SECRETKEY`: 腾讯云 Secret Key
|
||||||
|
* `CDN_DOMAIN`: CDN 域名,多个域名用逗号分隔
|
||||||
|
* `BARK_HOST`: [Bark](https://github.com/Finb/Bark) 消息通知 Host
|
||||||
|
* `BARK_KEY`: [Bark](https://github.com/Finb/Bark) 消息通知 Key
|
||||||
|
|
||||||
|
### 手动部署
|
||||||
|
|
||||||
|
#### 使用acme.sh申请证书
|
||||||
|
[安装及简单使用](https://blog.whuzfb.cn/blog/2020/07/07/web_https/#3-%E5%AE%89%E8%A3%85acme%E8%87%AA%E5%8A%A8%E7%AD%BE%E5%8F%91%E8%AF%81%E4%B9%A6)
|
||||||
|
对于本程序
|
||||||
|
```bash
|
||||||
|
# 腾讯云支持使用单域名和泛域名的证书,例如申请泛域名
|
||||||
|
acme.sh --issue -d "whuzfb.cn" -d "*.whuzfb.cn" --dns dns_dp
|
||||||
|
# 申请单域名
|
||||||
|
# acme.sh --issue -d "blog.whuzfb.cn" --dns dns_dp
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 修改config.example.py参数
|
||||||
|
根据注释修改每一项内容
|
||||||
|
然后重命名为`config.py`
|
||||||
|
|
||||||
|
## 主要函数
|
||||||
|
`ssl.get_cert_list(client)`:获取所有的SSL证书列表
|
||||||
|
`ssl.get_cert_info(client, cert_id)`:根据id获取SSL证书的信息
|
||||||
|
`ssl.get_cert_detail(client, cert_id)`:根据id获取SSL证书的详情
|
||||||
|
`ssl.delete_cert(client, cert_id)`:删除指定id的SSL证书
|
||||||
|
`ssl.upload_cert(client, local_cert_info)`:把本地的SSL证书上传到腾讯云,返回新证书的id
|
||||||
|
|
||||||
|
|
||||||
|
`cdn.get_cdn_detail_info(client)`:获取所有CDN的详细信息,返回列表
|
||||||
|
`cdn.get_cdn_url_push_info(client)`:查询CDN预热配额和每日可用量
|
||||||
|
`cdn.update_cdn_url_push(client, urls)`:指定 URL 资源列表加载至 CDN 节点,支持指定加速区域预热;默认情况下境内、境外每日预热 URL 限额为各 1000 条,每次最多可提交 20 条
|
||||||
|
`cdn.get_cdn_purge_url_info(client)`:查询CDN刷新配额和每日可用量
|
||||||
|
`cdn.update_cdn_purge_url(client, urls)`:指定 URL 资源的刷新,支持指定加速区域刷新;默认情况下境内、境外每日刷新 URL 限额为各 10000 条,每次最多可提交 1000 条
|
||||||
|
`cdn.update_cdn_ssl(client, domain, cert_id)`:为指定域名的CDN更换SSL证书
|
||||||
|
|
||||||
|
|
||||||
|
`ecdn.get_ecdn_basic_info(client)`:获取所有ECDN(全球加速服务)的基本信息,返回列表
|
||||||
|
`ecdn.get_ecdn_detail_info(client)`:获取所有ECDN的详细信息,返回列表
|
||||||
|
`ecdn.update_ecdn_ssl(client, domain, cert_id)`:为指定域名的CDN的更换SSL证书
|
0
api/__init__.py
Normal file
0
api/__init__.py
Normal file
213
api/cdn.py
Normal file
213
api/cdn.py
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# author: 'zfb'
|
||||||
|
# time: 2020-12-02 15:42
|
||||||
|
import json
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
|
||||||
|
# 导入 cdn 产品模块的 models
|
||||||
|
from tencentcloud.cdn.v20180606 import models
|
||||||
|
|
||||||
|
from api.get_client_profile import get_client_instance
|
||||||
|
|
||||||
|
def get_cdn_client_instance(id, key):
|
||||||
|
'''获取cdn的实例,用于后面对cdn的各种操作
|
||||||
|
'''
|
||||||
|
client = get_client_instance(id, key, "cdn")
|
||||||
|
return client
|
||||||
|
|
||||||
|
|
||||||
|
def get_cdn_detail_info(client):
|
||||||
|
'''获取所有CDN的详细信息,返回列表
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
req = models.DescribeDomainsConfigRequest()
|
||||||
|
# 参数列表为空:表示获取所有信息
|
||||||
|
# 部分可选参数
|
||||||
|
# Filters: Array Of DomainFilter, 查询条件过滤器,复杂类型
|
||||||
|
# filter = DomainFilter()
|
||||||
|
# filter.Name = "domain"
|
||||||
|
# filter.Value = [domain]
|
||||||
|
# filter.Fuzzy = False
|
||||||
|
params = {}
|
||||||
|
req.from_json_string(json.dumps(params))
|
||||||
|
|
||||||
|
resp = client.DescribeDomainsConfig(req)
|
||||||
|
# print(resp.to_json_string())
|
||||||
|
print("获取所有cdn详细信息成功")
|
||||||
|
return resp.Domains
|
||||||
|
|
||||||
|
except TencentCloudSDKException as err:
|
||||||
|
print(err)
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_cdn_basic_info(client, domain_name):
|
||||||
|
'''获取指定CDN的基本信息
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
req = models.DescribeDomainsRequest()
|
||||||
|
params = {
|
||||||
|
"Limit": 1,
|
||||||
|
"Filters": [
|
||||||
|
{
|
||||||
|
"Name": "domain",
|
||||||
|
"Value": [ domain_name ],
|
||||||
|
"Fuzzy": False
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
req.from_json_string(json.dumps(params))
|
||||||
|
|
||||||
|
resp = client.DescribeDomains(req)
|
||||||
|
# print(resp.to_json_string())
|
||||||
|
print("获取指定cdn基本信息成功")
|
||||||
|
return resp.Domains
|
||||||
|
|
||||||
|
except TencentCloudSDKException as err:
|
||||||
|
print(err)
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_cdn_url_push_info(client):
|
||||||
|
'''查询CDN预热配额和每日可用量
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
req = models.DescribePushQuotaRequest()
|
||||||
|
params = {}
|
||||||
|
req.from_json_string(json.dumps(params))
|
||||||
|
resp = client.DescribePushQuota(req)
|
||||||
|
# print(resp.to_json_string())
|
||||||
|
print("获取CDN预热配额和每日可用量信息成功")
|
||||||
|
return resp.UrlPush
|
||||||
|
|
||||||
|
except TencentCloudSDKException as err:
|
||||||
|
print(err)
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def update_cdn_url_push(client, urls, region):
|
||||||
|
'''指定 URL 资源列表加载至 CDN 节点,支持指定加速区域预热
|
||||||
|
默认情况下境内、境外每日预热 URL 限额为各 1000 条,每次最多可提交 20 条
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
req = models.PushUrlsCacheRequest()
|
||||||
|
params = {
|
||||||
|
"Urls": urls,
|
||||||
|
"Area": region,
|
||||||
|
}
|
||||||
|
req.from_json_string(json.dumps(params))
|
||||||
|
resp = client.PushUrlsCache(req)
|
||||||
|
print(resp.to_json_string())
|
||||||
|
print("URL:{}预热成功".format(', '.join(urls)))
|
||||||
|
return True
|
||||||
|
|
||||||
|
except TencentCloudSDKException as err:
|
||||||
|
print(err)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_cdn_purge_url_info(client):
|
||||||
|
'''查询CDN刷新URL配额和每日可用量
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
req = models.DescribePurgeQuotaRequest()
|
||||||
|
params = {}
|
||||||
|
req.from_json_string(json.dumps(params))
|
||||||
|
resp = client.DescribePurgeQuota(req)
|
||||||
|
# print(resp.to_json_string())
|
||||||
|
print("获取CDN刷新URL配额和每日可用量信息成功")
|
||||||
|
return resp.UrlPurge
|
||||||
|
|
||||||
|
except TencentCloudSDKException as err:
|
||||||
|
print(err)
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def update_cdn_purge_url(client, urls, region):
|
||||||
|
'''指定 URL 资源的刷新,支持指定加速区域刷新
|
||||||
|
默认情况下境内、境外每日刷新 URL 限额为各 10000 条,每次最多可提交 1000 条
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
req = models.PurgeUrlsCacheRequest()
|
||||||
|
params = {
|
||||||
|
"Urls": urls,
|
||||||
|
"Area": region,
|
||||||
|
"UrlEncode": True
|
||||||
|
}
|
||||||
|
req.from_json_string(json.dumps(params))
|
||||||
|
resp = client.PurgeUrlsCache(req)
|
||||||
|
print(resp.to_json_string())
|
||||||
|
print("URL:{}刷新成功".format(', '.join(urls)))
|
||||||
|
return True
|
||||||
|
|
||||||
|
except TencentCloudSDKException as err:
|
||||||
|
print(err)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def update_cdn_ssl(client, domain, cert_id):
|
||||||
|
'''为指定域名的CDN更换SSL证书
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
req = models.UpdateDomainConfigRequest()
|
||||||
|
# 必选参数
|
||||||
|
# Domain: String, 域名
|
||||||
|
# 部分可选参数
|
||||||
|
# Https: Https, Https 加速配置
|
||||||
|
# 该类型详见 https://cloud.tencent.com/document/api/228/30987#Https
|
||||||
|
timestr = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
params = {
|
||||||
|
"Domain": domain,
|
||||||
|
"Https": {
|
||||||
|
"Switch": "on",
|
||||||
|
"CertInfo": {
|
||||||
|
"CertId": cert_id,
|
||||||
|
"Message": "Auto update by api at {}".format(timestr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
req.from_json_string(json.dumps(params))
|
||||||
|
|
||||||
|
resp = client.UpdateDomainConfig(req)
|
||||||
|
print(resp.to_json_string())
|
||||||
|
print("成功更新域名为{0}的CDN的ssl证书为{1}".format(domain, cert_id))
|
||||||
|
|
||||||
|
except TencentCloudSDKException as err:
|
||||||
|
print(err)
|
||||||
|
exit("为CDN设置SSL证书{}出错".format(cert_id))
|
||||||
|
|
||||||
|
def update_cdn_https_options(client, domain, http2, hsts, age, hsts_subdomain, ocsp):
|
||||||
|
'''为指定域名的CDN的HTTPS开启HTTP 2.0、HSTS、OCSP等多个可选项
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
req = models.UpdateDomainConfigRequest()
|
||||||
|
params = {
|
||||||
|
"Domain": domain,
|
||||||
|
"Https": {
|
||||||
|
"Switch": "on"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if http2:
|
||||||
|
params["Https"]["Http2"] = "on"
|
||||||
|
if hsts:
|
||||||
|
params["Https"]["Hsts"] = {
|
||||||
|
"Switch": "off",
|
||||||
|
"MaxAge": 0,
|
||||||
|
"IncludeSubDomains": "off"
|
||||||
|
}
|
||||||
|
params["Https"]["Hsts"]["Switch"] = "on"
|
||||||
|
params["Https"]["Hsts"]["MaxAge"] = age
|
||||||
|
if hsts_subdomain:
|
||||||
|
params["Https"]["Hsts"]["IncludeSubDomains"] = "on"
|
||||||
|
if ocsp:
|
||||||
|
params["Https"]["OcspStapling"] = "on"
|
||||||
|
|
||||||
|
req.from_json_string(json.dumps(params))
|
||||||
|
|
||||||
|
resp = client.UpdateDomainConfig(req)
|
||||||
|
print(resp.to_json_string())
|
||||||
|
print("成功开启域名为{0}的CDN的HTTPS选项".format(domain))
|
||||||
|
|
||||||
|
except TencentCloudSDKException as err:
|
||||||
|
print(err)
|
||||||
|
exit("为{}的CDN开启HTTPS选项功能出错".format(domain))
|
79
api/ecdn.py
Normal file
79
api/ecdn.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# author: 'zfb'
|
||||||
|
# time: 2020-12-02 15:50
|
||||||
|
import json
|
||||||
|
|
||||||
|
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
|
||||||
|
# 导入 ecdn 产品模块的 models
|
||||||
|
from tencentcloud.ecdn.v20191012 import models
|
||||||
|
|
||||||
|
from api.get_client_profile import get_client_instance
|
||||||
|
|
||||||
|
def get_ecdn_client_instance(id, key):
|
||||||
|
'''获取ecdn的实例,用于后面对ecdn的各种操作
|
||||||
|
'''
|
||||||
|
client = get_client_instance(id, key, "ecdn")
|
||||||
|
return client
|
||||||
|
|
||||||
|
|
||||||
|
def get_ecdn_basic_info(client):
|
||||||
|
'''获取所有ECDN的基本信息,返回列表
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
req = models.DescribeDomainsRequest()
|
||||||
|
params = {}
|
||||||
|
req.from_json_string(json.dumps(params))
|
||||||
|
resp = client.DescribeDomains(req)
|
||||||
|
# print(resp.to_json_string())
|
||||||
|
print("获取所有ecdn基本信息成功")
|
||||||
|
return resp.Domains
|
||||||
|
|
||||||
|
except TencentCloudSDKException as err:
|
||||||
|
print(err)
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_ecdn_detail_info(client):
|
||||||
|
'''获取所有ECDN的详细信息,返回列表
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
req = models.DescribeDomainsConfigRequest()
|
||||||
|
params = {}
|
||||||
|
req.from_json_string(json.dumps(params))
|
||||||
|
resp = client.DescribeDomainsConfig(req)
|
||||||
|
# print(resp.to_json_string())
|
||||||
|
print("获取所有ecdn详细信息成功")
|
||||||
|
return resp.Domains
|
||||||
|
|
||||||
|
except TencentCloudSDKException as err:
|
||||||
|
print(err)
|
||||||
|
return []
|
||||||
|
|
||||||
|
def update_ecdn_ssl(client, domain, cert_id):
|
||||||
|
'''为指定域名的CDN的更换SSL证书
|
||||||
|
'''
|
||||||
|
# 为ecdn更新证书,使用ecdn相关接口
|
||||||
|
# https://console.cloud.tencent.com/api/explorer?Product=ecdn&Version=2019-10-12
|
||||||
|
try:
|
||||||
|
req = models.UpdateDomainConfigRequest()
|
||||||
|
# 必选参数
|
||||||
|
# Domain: String, 域名
|
||||||
|
# 部分可选参数
|
||||||
|
# Https: Https, Https 加速配置
|
||||||
|
# 该类型详见 https://cloud.tencent.com/document/api/228/30987#Https
|
||||||
|
params = {
|
||||||
|
"Domain": domain,
|
||||||
|
"Https": {
|
||||||
|
"CertInfo": {
|
||||||
|
"CertId": cert_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
req.from_json_string(json.dumps(params))
|
||||||
|
resp = client.UpdateDomainConfig(req)
|
||||||
|
print(resp.to_json_string())
|
||||||
|
print("成功更新域名为{0}的CDN的ssl证书为{1}".format(domain, cert_id))
|
||||||
|
|
||||||
|
except TencentCloudSDKException as err:
|
||||||
|
print(err)
|
||||||
|
exit("为CDN设置SSL证书{}出错".format(cert_id))
|
52
api/get_client_profile.py
Normal file
52
api/get_client_profile.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# author: 'zfb'
|
||||||
|
# time: 2020-12-02 15:17
|
||||||
|
|
||||||
|
from tencentcloud.common import credential
|
||||||
|
# 导入可选配置类
|
||||||
|
from tencentcloud.common.profile.client_profile import ClientProfile
|
||||||
|
from tencentcloud.common.profile.http_profile import HttpProfile
|
||||||
|
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
|
||||||
|
# 导入ssl产品模块的 client
|
||||||
|
from tencentcloud.ssl.v20191205 import ssl_client
|
||||||
|
# 导入cdn产品模块的 client
|
||||||
|
from tencentcloud.cdn.v20180606 import cdn_client
|
||||||
|
# 导入ecdn产品模块的 client
|
||||||
|
from tencentcloud.ecdn.v20191012 import ecdn_client
|
||||||
|
|
||||||
|
def get_client_instance(id, key, product):
|
||||||
|
'''获取指定endpoint的实例,用于后面对其的各种操作
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
# 实例化一个认证对象,入参需要传入腾讯云账户 secretId,secretKey, 此处还需注意密钥对的保密
|
||||||
|
cred = credential.Credential(id, key)
|
||||||
|
|
||||||
|
# 实例化一个 http 选项,可选
|
||||||
|
httpProfile = HttpProfile()
|
||||||
|
# post 请求 (默认为 post 请求)
|
||||||
|
httpProfile.reqMethod = "POST"
|
||||||
|
# 请求超时时间,单位为秒 (默认60秒)
|
||||||
|
httpProfile.reqTimeout = 30
|
||||||
|
# 不指定接入地域域名 (默认就近接入)
|
||||||
|
httpProfile.endpoint = "{}.tencentcloudapi.com".format(product)
|
||||||
|
|
||||||
|
# 实例化一个 client 选项,可选
|
||||||
|
clientProfile = ClientProfile()
|
||||||
|
clientProfile.httpProfile = httpProfile
|
||||||
|
# 实例化要请求产品的 client 对象,clientProfile 是可选的
|
||||||
|
if product == "ssl":
|
||||||
|
client = ssl_client.SslClient(cred, "", clientProfile)
|
||||||
|
print("实例化一个ssl_client成功")
|
||||||
|
elif product == "cdn":
|
||||||
|
client = cdn_client.CdnClient(cred, "", clientProfile)
|
||||||
|
print("实例化cdn client成功")
|
||||||
|
elif product == "ecdn":
|
||||||
|
client = ecdn_client.EcdnClient(cred, "", clientProfile)
|
||||||
|
print("实例化ecdn client成功")
|
||||||
|
else:
|
||||||
|
exit("本程序仅支持ssl、cdn、ecdn")
|
||||||
|
return client
|
||||||
|
except TencentCloudSDKException as err:
|
||||||
|
print(err)
|
||||||
|
exit(-1)
|
145
api/ssl.py
Normal file
145
api/ssl.py
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# author: 'zfb'
|
||||||
|
# time: 2020-12-02 15:02
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
|
||||||
|
# 导入 ssl 产品模块的 models
|
||||||
|
from tencentcloud.ssl.v20191205 import models
|
||||||
|
|
||||||
|
from api.get_client_profile import get_client_instance
|
||||||
|
|
||||||
|
def get_ssl_client_instance(id, key):
|
||||||
|
'''获取ssl的实例,用于后面对ssl的各种操作
|
||||||
|
'''
|
||||||
|
client = get_client_instance(id, key, "ssl")
|
||||||
|
return client
|
||||||
|
|
||||||
|
|
||||||
|
def get_cert_list(client):
|
||||||
|
'''获取所有的SSL证书列表
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
# 实例化一个 ssl 实例信息查询请求对象,每个接口都会对应一个 request 对象
|
||||||
|
req = models.DescribeCertificatesRequest()
|
||||||
|
# 可选参数列表
|
||||||
|
# Offset: Integer, 分页偏移量,从0开始
|
||||||
|
# Limit: Integer, 每页数量,默认20
|
||||||
|
# SearchKey: String, 搜索关键词,可搜索证书 ID、备注名称、域名
|
||||||
|
# CertificateType: String, 证书类型:CA = 客户端证书,SVR = 服务器证书
|
||||||
|
# ProjectId: Integer, 项目 ID
|
||||||
|
# ExpirationSort: String, 按到期时间排序:DESC = 降序, ASC = 升序
|
||||||
|
# CertificateStatus: Array Of Integer, 证书状态
|
||||||
|
# Deployable: Integer, 是否可部署,可选值:1 = 可部署,0 = 不可部署
|
||||||
|
params = {}
|
||||||
|
req.from_json_string(json.dumps(params))
|
||||||
|
|
||||||
|
# 通过 client 对象调用 DescribeCertificatesRequest 方法发起请求,请求方法名与请求对象对应
|
||||||
|
# 返回的 resp 是一个 DescribeCertificatesResponse 类的实例,与请求对象对应
|
||||||
|
resp = client.DescribeCertificates(req)
|
||||||
|
# 输出 json 格式的字符串回包
|
||||||
|
# print(resp.to_json_string())
|
||||||
|
# 也可以取出单个值,通过官网接口文档或跳转到 response 对象的定义处查看返回字段的定义
|
||||||
|
# print(resp.TotalCount)
|
||||||
|
print("获取ssl证书列表成功")
|
||||||
|
return resp.Certificates
|
||||||
|
except TencentCloudSDKException as err:
|
||||||
|
print(err)
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def get_cert_info(client, cert_id):
|
||||||
|
'''根据id获取SSL证书的信息
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
req = models.DescribeCertificateRequest()
|
||||||
|
# 必选参数
|
||||||
|
# CertificateId: String, 证书 ID
|
||||||
|
params = {
|
||||||
|
"CertificateId": cert_id
|
||||||
|
}
|
||||||
|
req.from_json_string(json.dumps(params))
|
||||||
|
|
||||||
|
resp = client.DescribeCertificate(req)
|
||||||
|
# print(resp.to_json_string())
|
||||||
|
print("获取ssl证书{}的信息成功".format(cert_id))
|
||||||
|
return resp
|
||||||
|
|
||||||
|
except TencentCloudSDKException as err:
|
||||||
|
print(err)
|
||||||
|
exit("获取证书{}信息出错".format(cert_id))
|
||||||
|
|
||||||
|
|
||||||
|
def get_cert_detail(client, cert_id):
|
||||||
|
'''根据id获取SSL证书的详情
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
req = models.DescribeCertificateDetailRequest()
|
||||||
|
# 必选参数
|
||||||
|
# CertificateId: String, 证书 ID
|
||||||
|
params = {
|
||||||
|
"CertificateId": cert_id
|
||||||
|
}
|
||||||
|
req.from_json_string(json.dumps(params))
|
||||||
|
|
||||||
|
resp = client.DescribeCertificateDetail(req)
|
||||||
|
# print(resp.to_json_string())
|
||||||
|
print("获取ssl证书{}的详细信息成功".format(cert_id))
|
||||||
|
|
||||||
|
except TencentCloudSDKException as err:
|
||||||
|
print(err)
|
||||||
|
exit("获取证书{}详细信息出错".format(cert_id))
|
||||||
|
|
||||||
|
|
||||||
|
def delete_cert(client, cert_id):
|
||||||
|
'''删除指定id的SSL证书(删除不存在的id会出现警告)
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
req = models.DeleteCertificateRequest()
|
||||||
|
# 必选参数
|
||||||
|
# CertificateId: String, 证书 ID
|
||||||
|
params = {
|
||||||
|
"CertificateId": cert_id
|
||||||
|
}
|
||||||
|
req.from_json_string(json.dumps(params))
|
||||||
|
|
||||||
|
resp = client.DeleteCertificate(req)
|
||||||
|
# print(resp.to_json_string())
|
||||||
|
print("删除ssl证书{}成功".format(cert_id))
|
||||||
|
|
||||||
|
except TencentCloudSDKException as err:
|
||||||
|
print(err)
|
||||||
|
exit("删除证书{}出错".format(cert_id))
|
||||||
|
|
||||||
|
|
||||||
|
def upload_cert(client, local_cert_info):
|
||||||
|
'''把本地的SSL证书上传到腾讯云,返回新证书的id
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
req = models.UploadCertificateRequest()
|
||||||
|
# 必选参数
|
||||||
|
# CertificatePublicKey: String, 证书公钥内容
|
||||||
|
# CertificatePrivateKey: String, 私钥内容,证书类型为 SVR 时必填,为 CA 时可不填
|
||||||
|
# 可选参数列表
|
||||||
|
# CertificateType: String, 证书类型,默认 SVR。CA = 客户端证书,SVR = 服务器证书
|
||||||
|
# Alias: String, 备注名称
|
||||||
|
# ProjectId: Integer, 项目 ID
|
||||||
|
timestr = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
params = {
|
||||||
|
"CertificatePublicKey": local_cert_info["cer"],
|
||||||
|
"CertificatePrivateKey": local_cert_info["key"],
|
||||||
|
"CertificateType": local_cert_info["type"],
|
||||||
|
"Alias": "Auto upload by api at {}".format(timestr)
|
||||||
|
}
|
||||||
|
req.from_json_string(json.dumps(params))
|
||||||
|
|
||||||
|
resp = client.UploadCertificate(req)
|
||||||
|
# print(resp.to_json_string())
|
||||||
|
print("上传ssl证书成功")
|
||||||
|
return resp.CertificateId
|
||||||
|
|
||||||
|
except TencentCloudSDKException as err:
|
||||||
|
print(err)
|
||||||
|
return ""
|
85
api/tools.py
Normal file
85
api/tools.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# author: 'zfb'
|
||||||
|
# time: 2020-12-02 15:45
|
||||||
|
|
||||||
|
def read_file(name):
|
||||||
|
'''读取文件内容
|
||||||
|
'''
|
||||||
|
with open(name, 'r') as file:
|
||||||
|
text = file.read()
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def chunks(l, n):
|
||||||
|
for i in range(0, len(l), n):
|
||||||
|
yield l[i:i + n]
|
||||||
|
|
||||||
|
|
||||||
|
def resize_url_list(url_list, group_size):
|
||||||
|
'''将一维列表按照指定长度分割
|
||||||
|
'''
|
||||||
|
url_chunks = list(chunks(url_list, group_size))
|
||||||
|
results = []
|
||||||
|
for i in range(len(url_chunks)):
|
||||||
|
results.append(url_chunks[i])
|
||||||
|
print("重置的URL列表个数{},每个列表包含文件数{}".format(len(results), group_size))
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def get_sitemap_urls(url):
|
||||||
|
'''从给定的sitemap.xml文件获取链接
|
||||||
|
'''
|
||||||
|
import requests
|
||||||
|
import re
|
||||||
|
text = requests.get(url).text
|
||||||
|
pattern = re.compile(r'<loc>(.*?)</loc>')
|
||||||
|
results = re.findall(pattern, text)
|
||||||
|
url_list = []
|
||||||
|
for res in results:
|
||||||
|
if not res.endswith("/"):
|
||||||
|
res = res + "/"
|
||||||
|
url_list.append(res)
|
||||||
|
return url_list
|
||||||
|
|
||||||
|
|
||||||
|
def get_urls_from_file(file_name):
|
||||||
|
'''从给定的文件获取链接
|
||||||
|
'''
|
||||||
|
with open(file_name, 'r') as file:
|
||||||
|
return [x.strip() for x in file.readlines()]
|
||||||
|
|
||||||
|
def generate_https(https):
|
||||||
|
'''由于Https无法序列化,自己将其改为字典(已弃用)
|
||||||
|
'''
|
||||||
|
server_cert = {}
|
||||||
|
server_cert["CertId"] = https.CertInfo.CertId
|
||||||
|
server_cert["CertName"] = https.CertInfo.CertName
|
||||||
|
server_cert["Certificate"] = https.CertInfo.Certificate
|
||||||
|
server_cert["PrivateKey"] = https.CertInfo.PrivateKey
|
||||||
|
server_cert["ExpireTime"] = https.CertInfo.ExpireTime
|
||||||
|
server_cert["DeployTime"] = https.CertInfo.DeployTime
|
||||||
|
server_cert["Message"] = https.CertInfo.Message
|
||||||
|
|
||||||
|
client_cert = {}
|
||||||
|
client_cert["Certificate"] = https.ClientCertInfo.Certificate
|
||||||
|
client_cert["CertName"] = https.ClientCertInfo.CertName
|
||||||
|
client_cert["ExpireTime"] = https.ClientCertInfo.ExpireTime
|
||||||
|
client_cert["DeployTime"] = https.ClientCertInfo.DeployTime
|
||||||
|
|
||||||
|
hsts = {}
|
||||||
|
hsts["Switch"] = https.Hsts.Switch
|
||||||
|
hsts["MaxAge"] = https.Hsts.MaxAge
|
||||||
|
hsts["IncludeSubDomains"] = https.Hsts.IncludeSubDomains
|
||||||
|
|
||||||
|
res = {}
|
||||||
|
res["Switch"] = https.Switch
|
||||||
|
res["Http2"] = https.Http2
|
||||||
|
res["OcspStapling"] = https.OcspStapling
|
||||||
|
res["VerifyClient"] = https.VerifyClient
|
||||||
|
res["Spdy"] = https.Spdy
|
||||||
|
res["SslStatus"] = https.SslStatus
|
||||||
|
res["CertInfo"] = server_cert
|
||||||
|
res["ClientCertInfo"] = client_cert
|
||||||
|
res["Hsts"] = https.Hsts
|
||||||
|
return res
|
55
config.example.py
Normal file
55
config.example.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# author: 'zfb'
|
||||||
|
# time: 2020-12-02 16:15
|
||||||
|
|
||||||
|
# 腾讯云支持使用单域名和泛域名的证书,例如
|
||||||
|
# acme.sh --issue -d "whuzfb.cn" -d "*.whuzfb.cn" --dns dns_dp
|
||||||
|
# acme.sh --issue -d "blog.whuzfb.cn" --dns dns_dp
|
||||||
|
|
||||||
|
# 使用ACME申请的SSL完整证书的本地存放路径
|
||||||
|
CER_FILE = "/home/zfb/.acme.sh/whuzfb.cn/fullchain.cer"
|
||||||
|
|
||||||
|
# 使用ACME申请的SSL证书私钥的本地存放路径
|
||||||
|
KEY_FILE = "/home/zfb/.acme.sh/whuzfb.cn/whuzfb.cn.key"
|
||||||
|
|
||||||
|
# CDN服务配置的域名(需要提前在腾讯云网页前端创建)
|
||||||
|
# 如果ACME申请的证书为泛域名证书,且要配置多个CDN加速
|
||||||
|
# CDN_DOMAIN = ["blog.whuzfb.cn", "blog2.whuzfb.cn", "web.whuzfb.cn"]
|
||||||
|
CDN_DOMAIN = ["blog.whuzfb.cn"]
|
||||||
|
|
||||||
|
# 腾讯云:https://console.cloud.tencent.com/cam/capi
|
||||||
|
SECRETID = "AKeee5555512345677777123456788889876"
|
||||||
|
SECRETKEY = "A71234567890abcdefedcba098765432"
|
||||||
|
|
||||||
|
# 控制功能开关
|
||||||
|
# 是否进行上传证书文件的操作(根据CER_FILE和KEY_FILE)
|
||||||
|
UPLOAD_SSL = True
|
||||||
|
# 以下为HTTPS额外功能
|
||||||
|
# 是否开启HTTP2
|
||||||
|
ENABLE_HTTP2 = True
|
||||||
|
# 是否开启HSTS
|
||||||
|
ENABLE_HSTS = True
|
||||||
|
# 为HSTS设定最长过期时间(以秒为单位)
|
||||||
|
HSTS_TIMEOUT_AGE = 1
|
||||||
|
# HSTS包含子域名(仅对泛域名有效)
|
||||||
|
HSTS_INCLUDE_SUBDOMAIN = True
|
||||||
|
# 是否开启OCSP
|
||||||
|
ENABLE_OCSP = True
|
||||||
|
# 是否删除适用于CDN_DOMAIN域名下的其他所有证书
|
||||||
|
# 满足以下条件:证书适用于CDN_DOMAIN、证书id不是本次使用的id
|
||||||
|
DELETE_OLD_CERTS = True
|
||||||
|
|
||||||
|
# 是否进行为CDN_DOMAIN更换SSL证书的操作
|
||||||
|
# 若UPDATE_SSL = True且UPLOAD_SSL = True,则CERT_ID可不设置,直接利用UPLOAD_SSL的证书
|
||||||
|
UPDATE_SSL = True
|
||||||
|
CERT_ID = ""
|
||||||
|
# 是否进行预热URL的操作
|
||||||
|
PUSH_URL = True
|
||||||
|
# 是否进行刷新URL的操作
|
||||||
|
PURGE_URL = True
|
||||||
|
# 自定义的预热URL(默认会预热sitemap.xml的所有链接)文件路径
|
||||||
|
# 该文件内,每行一个URL,例如
|
||||||
|
# https://blog.whuzfb.cn/img/me2.jpg
|
||||||
|
# https://blog.whuzfb.cn/img/home-bg.jpg
|
||||||
|
URLS_FILE = "urls.txt"
|
17
docker/entrypoint.sh
Normal file
17
docker/entrypoint.sh
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# 添加crontab配置
|
||||||
|
cat > /var/spool/cron/crontabs/root <<-EOF
|
||||||
|
0 2 1 * * /data/update.sh &> /data/run.log
|
||||||
|
EOF
|
||||||
|
# 启动crontab服务
|
||||||
|
crond
|
||||||
|
# 马上执行证书更新
|
||||||
|
if [ ! -f /data/run.log ]; then
|
||||||
|
touch /data/run.log
|
||||||
|
fi
|
||||||
|
if [ "${RUN_NOW:-"false"}" = "true" ]; then
|
||||||
|
nohup /data/update.sh &> /data/run.log &
|
||||||
|
fi
|
||||||
|
|
||||||
|
tail -f /data/run.log
|
75
docker/update.sh
Normal file
75
docker/update.sh
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
if [ "${ACME_ENABLED:=true}" = "true" ]; then
|
||||||
|
# 使用acme获取/更新证书
|
||||||
|
${ACME_HOME}/acme.sh ${ACME_PARAMS:-} --force --issue --cert-home ${CERT_HOME} -d ${ACME_DOMAIN} -d *.${ACME_DOMAIN} --dns ${ACME_DNS_TYPE}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 添加刷新url
|
||||||
|
echo "${PUSH_URLS:-}" | tr ',' '\n' > ${WORK_DIR}/urls.txt
|
||||||
|
if [ -n "${PUSH_URLS_PATH:-}" ]; then
|
||||||
|
cat "${PUSH_URLS_PATH}" >> ${WORK_DIR}/urls.txt
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 写入腾讯云cdn更新配置
|
||||||
|
cat>${WORK_DIR}/config.py<<-EOF
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# author: 'zfb'
|
||||||
|
# time: 2020-12-02 16:15
|
||||||
|
|
||||||
|
# 腾讯云支持使用单域名和泛域名的证书,例如
|
||||||
|
# acme.sh --issue -d "whuzfb.cn" -d "*.whuzfb.cn" --dns dns_dp
|
||||||
|
# acme.sh --issue -d "blog.whuzfb.cn" --dns dns_dp
|
||||||
|
|
||||||
|
# 使用ACME申请的SSL完整证书的本地存放路径
|
||||||
|
CER_FILE = "${CERT_HOME}/${ACME_DOMAIN}/fullchain.cer"
|
||||||
|
|
||||||
|
# 使用ACME申请的SSL证书私钥的本地存放路径
|
||||||
|
KEY_FILE = "${CERT_HOME}/${ACME_DOMAIN}/${ACME_DOMAIN}.key"
|
||||||
|
|
||||||
|
# CDN服务配置的域名(需要提前在腾讯云网页前端创建)
|
||||||
|
# 如果ACME申请的证书为泛域名证书,且要配置多个CDN加速
|
||||||
|
# CDN_DOMAIN = ["blog.whuzfb.cn", "blog2.whuzfb.cn", "web.whuzfb.cn"]
|
||||||
|
CDN_DOMAIN = ["`echo ${CDN_DOMAIN} | sed -e 's/,/","/g'`"]
|
||||||
|
|
||||||
|
# 腾讯云:https://console.cloud.tencent.com/cam/capi
|
||||||
|
SECRETID = "${SECRETID}"
|
||||||
|
SECRETKEY = "${SECRETKEY}"
|
||||||
|
|
||||||
|
# 控制功能开关
|
||||||
|
# 是否进行上传证书文件的操作(根据CER_FILE和KEY_FILE)
|
||||||
|
UPLOAD_SSL = True
|
||||||
|
# 以下为HTTPS额外功能
|
||||||
|
# 是否开启HTTP2
|
||||||
|
ENABLE_HTTP2 = True
|
||||||
|
# 是否开启HSTS
|
||||||
|
ENABLE_HSTS = True
|
||||||
|
# 为HSTS设定最长过期时间(以秒为单位)
|
||||||
|
HSTS_TIMEOUT_AGE = 31536000
|
||||||
|
# HSTS包含子域名(仅对泛域名有效)
|
||||||
|
HSTS_INCLUDE_SUBDOMAIN = True
|
||||||
|
# 是否开启OCSP
|
||||||
|
ENABLE_OCSP = True
|
||||||
|
# 是否删除适用于CDN_DOMAIN域名下的其他所有证书
|
||||||
|
# 满足以下条件:证书适用于CDN_DOMAIN、证书id不是本次使用的id
|
||||||
|
DELETE_OLD_CERTS = True
|
||||||
|
|
||||||
|
# 是否进行为CDN_DOMAIN更换SSL证书的操作
|
||||||
|
# 若UPDATE_SSL = True且UPLOAD_SSL = True,则CERT_ID可不设置,直接利用UPLOAD_SSL的证书
|
||||||
|
UPDATE_SSL = True
|
||||||
|
CERT_ID = ""
|
||||||
|
# 是否进行预热URL的操作
|
||||||
|
PUSH_URL = True
|
||||||
|
# 是否进行刷新URL的操作
|
||||||
|
PURGE_URL = True
|
||||||
|
# 自定义的预热URL(默认会预热sitemap.xml的所有链接)文件路径
|
||||||
|
# 该文件内,每行一个URL,例如
|
||||||
|
# https://blog.whuzfb.cn/img/me2.jpg
|
||||||
|
# https://blog.whuzfb.cn/img/home-bg.jpg
|
||||||
|
URLS_FILE = "urls.txt"
|
||||||
|
EOF
|
||||||
|
# 更新CDN证书
|
||||||
|
cd ${WORK_DIR} && python main.py
|
199
main.py
Normal file
199
main.py
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# author: 'zfb'
|
||||||
|
# time: 2020-12-02 15:56
|
||||||
|
from api import cdn, ecdn, ssl, tools
|
||||||
|
import config
|
||||||
|
|
||||||
|
def run_config_ssl(id, key, cer_file, key_file):
|
||||||
|
'''上传SSl证书到腾讯云SSL证书管理,返回新证书的id
|
||||||
|
'''
|
||||||
|
cert_info = {
|
||||||
|
# Let's Encrypt 是通过中级 CA 机构颁发的证书,拿到的证书文件包含
|
||||||
|
# whuzfb.cn.cer ca.cer
|
||||||
|
# 需要人为地将服务器证书与中间证书拼接在一起(acme.sh已经进行拼接)
|
||||||
|
# fullchain.cer
|
||||||
|
"cer": tools.read_file(cer_file),
|
||||||
|
"key": tools.read_file(key_file),
|
||||||
|
"type": "CA"
|
||||||
|
}
|
||||||
|
ssl_client = ssl.get_ssl_client_instance(id, key)
|
||||||
|
cert_list = ssl.get_cert_list(ssl_client)
|
||||||
|
for cert in cert_list:
|
||||||
|
# 获取每个证书的id
|
||||||
|
cert_id = cert.CertificateId
|
||||||
|
# 获取每个证书的信息
|
||||||
|
# if domain == ssl.get_cert_info(ssl_client, cert_id).Domain:
|
||||||
|
# # 删除证书
|
||||||
|
# # delete_cert(client, cert_id)
|
||||||
|
# # break
|
||||||
|
# print(cert_id)
|
||||||
|
# 上传证书,获取新证书的id
|
||||||
|
id = ssl.upload_cert(ssl_client, cert_info)
|
||||||
|
if len(id)>0:
|
||||||
|
return id
|
||||||
|
else:
|
||||||
|
exit("获取新证书id失败")
|
||||||
|
|
||||||
|
|
||||||
|
def run_config_cdn(id, key, domain, cert_id):
|
||||||
|
'''该函数实现为CDN更新ssl证书的功能
|
||||||
|
'''
|
||||||
|
cdn_client = cdn.get_cdn_client_instance(id, key)
|
||||||
|
cdns = cdn.get_cdn_detail_info(cdn_client)
|
||||||
|
https = None
|
||||||
|
for _cdn in cdns:
|
||||||
|
if _cdn.Domain == domain:
|
||||||
|
https = _cdn.Https
|
||||||
|
break
|
||||||
|
print(https)
|
||||||
|
# generate_https(https)
|
||||||
|
cdn.update_cdn_ssl(cdn_client, domain, cert_id)
|
||||||
|
|
||||||
|
def https_options_enabler(id, key, domain, http2, hsts, age, hsts_subdomain, ocsp):
|
||||||
|
'''开启HTTPS配置中的部分选项
|
||||||
|
'''
|
||||||
|
cdn_client = cdn.get_cdn_client_instance(id, key)
|
||||||
|
cdn.update_cdn_https_options(cdn_client, domain, http2, hsts, age, hsts_subdomain, ocsp)
|
||||||
|
|
||||||
|
def delete_old_ssls(id, key, cdn_domain, ignore_id):
|
||||||
|
'''删除某个CDN的,除ignore_id以外的所有ssl证书
|
||||||
|
'''
|
||||||
|
ssl_client = ssl.get_ssl_client_instance(id, key)
|
||||||
|
cert_list = ssl.get_cert_list(ssl_client)
|
||||||
|
for cert in cert_list:
|
||||||
|
cert_id = cert.CertificateId
|
||||||
|
# 刚上传的这个证书不删除
|
||||||
|
if cert_id == ignore_id:
|
||||||
|
continue
|
||||||
|
cert_info = ssl.get_cert_info(ssl_client, cert_id)
|
||||||
|
cert_domain_and_alt_name = [cert_info.Domain] + cert_info.SubjectAltName
|
||||||
|
matched = False
|
||||||
|
# 判断域名匹配
|
||||||
|
for cert_name in cert_domain_and_alt_name:
|
||||||
|
if cert_name:
|
||||||
|
# 判断主域名或多域名
|
||||||
|
if cert_name == cdn_domain:
|
||||||
|
matched = True
|
||||||
|
break
|
||||||
|
# 判断泛域名 m=['*','example.cn']
|
||||||
|
m = cert_name.split('.', 1)
|
||||||
|
n = cdn_domain.split('.', 1)
|
||||||
|
if m[0] == "*" and m[1] == n[1]:
|
||||||
|
matched = True
|
||||||
|
break
|
||||||
|
# 根据结果删除证书
|
||||||
|
if matched:
|
||||||
|
ssl.delete_cert(ssl_client, cert_id)
|
||||||
|
|
||||||
|
|
||||||
|
def run_config_ecdn(id, key, domain, cert_id):
|
||||||
|
'''全站加速网络:为指定域名的CDN更新SSL证书
|
||||||
|
'''
|
||||||
|
ecdn_client = ecdn.get_ecdn_client_instance(id, key)
|
||||||
|
ecdn.get_ecdn_basic_info(ecdn_client)
|
||||||
|
cdns = ecdn.get_ecdn_detail_info(ecdn_client)
|
||||||
|
ecdn.update_ecdn_ssl(ecdn_client, domain, cert_id)
|
||||||
|
|
||||||
|
|
||||||
|
def run_url_push(id, key, domain, urls_file):
|
||||||
|
'''为CDN推送预热URL
|
||||||
|
'''
|
||||||
|
from time import sleep
|
||||||
|
from os.path import isfile
|
||||||
|
urls = []
|
||||||
|
try:
|
||||||
|
urls = tools.get_sitemap_urls("https://{}/sitemap.xml".format(domain))
|
||||||
|
except Exception as e:
|
||||||
|
print(repr(e))
|
||||||
|
if isfile(urls_file):
|
||||||
|
urls = urls + tools.get_urls_from_file(urls_file)
|
||||||
|
cdn_client = cdn.get_cdn_client_instance(id, key)
|
||||||
|
cdn_region = cdn.get_cdn_basic_info(cdn_client, domain)[0].Area
|
||||||
|
# 预热URL支持area为global的参数,但是为了方便统计用量配额,手动分开刷新
|
||||||
|
if cdn_region == 'global':
|
||||||
|
cdn_region = ['mainland', 'overseas']
|
||||||
|
else:
|
||||||
|
cdn_region = [cdn_region]
|
||||||
|
info = cdn.get_cdn_url_push_info(cdn_client)
|
||||||
|
# 统计预热url数量
|
||||||
|
cnt = 0
|
||||||
|
# 根据加速域名配置的区域进行预热
|
||||||
|
for i in info:
|
||||||
|
if i.Area in cdn_region:
|
||||||
|
grp_size = i.Batch
|
||||||
|
available = i.Available
|
||||||
|
print("正在对区域{0}进行url预热,剩余配额{1}条".format(i.Area, available))
|
||||||
|
new_urls = tools.resize_url_list(urls, grp_size)
|
||||||
|
for url_grp in new_urls:
|
||||||
|
res = cdn.update_cdn_url_push(cdn_client, url_grp, i.Area)
|
||||||
|
if res:
|
||||||
|
cnt = cnt + len(url_grp)
|
||||||
|
sleep(0.1)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
print("成功预热{}个URL".format(cnt))
|
||||||
|
|
||||||
|
|
||||||
|
def run_purge_url(id, key, domain, urls_file):
|
||||||
|
'''为CDN推送刷新URL
|
||||||
|
'''
|
||||||
|
from time import sleep
|
||||||
|
from os.path import isfile
|
||||||
|
urls = []
|
||||||
|
try:
|
||||||
|
urls = tools.get_sitemap_urls("https://{}/sitemap.xml".format(domain))
|
||||||
|
except Exception as e:
|
||||||
|
print(repr(e))
|
||||||
|
if isfile(urls_file):
|
||||||
|
urls = urls + tools.get_urls_from_file(urls_file)
|
||||||
|
cdn_client = cdn.get_cdn_client_instance(id, key)
|
||||||
|
cdn_region = cdn.get_cdn_basic_info(cdn_client, domain)[0].Area
|
||||||
|
# 刷新URL不支持area为global的参数
|
||||||
|
if cdn_region == 'global':
|
||||||
|
cdn_region = ['mainland', 'overseas']
|
||||||
|
else:
|
||||||
|
cdn_region = [cdn_region]
|
||||||
|
info = cdn.get_cdn_purge_url_info(cdn_client)
|
||||||
|
# 统计刷新url数量
|
||||||
|
cnt = 0
|
||||||
|
# 根据加速域名配置的区域进行刷新
|
||||||
|
for i in info:
|
||||||
|
if i.Area in cdn_region:
|
||||||
|
grp_size = i.Batch
|
||||||
|
available = i.Available
|
||||||
|
print("正在对区域{0}进行url刷新,剩余配额{1}条".format(i.Area, available))
|
||||||
|
new_urls = tools.resize_url_list(urls, grp_size)
|
||||||
|
for url_grp in new_urls:
|
||||||
|
res = cdn.update_cdn_purge_url(cdn_client, url_grp, i.Area)
|
||||||
|
if res:
|
||||||
|
cnt = cnt + len(url_grp)
|
||||||
|
sleep(0.1)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
print("成功刷新{}个URL".format(cnt))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
SECRETID = config.SECRETID
|
||||||
|
SECRETKEY = config.SECRETKEY
|
||||||
|
# 泛域名证书
|
||||||
|
if config.UPLOAD_SSL:
|
||||||
|
cert_id = run_config_ssl(SECRETID, SECRETKEY, config.CER_FILE, config.KEY_FILE)
|
||||||
|
else:
|
||||||
|
cert_id = config.CERT_ID
|
||||||
|
for my_domain in config.CDN_DOMAIN:
|
||||||
|
if config.UPDATE_SSL:
|
||||||
|
run_config_cdn(SECRETID, SECRETKEY, my_domain, cert_id)
|
||||||
|
if config.ENABLE_HSTS or config.ENABLE_OCSP or config.ENABLE_HTTP2:
|
||||||
|
https_options_enabler(SECRETID, SECRETKEY, my_domain, config.ENABLE_HTTP2, config.ENABLE_HSTS,
|
||||||
|
config.HSTS_TIMEOUT_AGE, config.HSTS_INCLUDE_SUBDOMAIN, config.ENABLE_OCSP)
|
||||||
|
if config.DELETE_OLD_CERTS:
|
||||||
|
delete_old_ssls(SECRETID, SECRETKEY, my_domain, cert_id)
|
||||||
|
if config.PUSH_URL:
|
||||||
|
run_url_push(SECRETID, SECRETKEY, my_domain, config.URLS_FILE)
|
||||||
|
if config.PURGE_URL:
|
||||||
|
run_purge_url(SECRETID, SECRETKEY, my_domain, config.URLS_FILE)
|
||||||
|
# ecdn是全球加速服务,与CDN不同,本账号没有开通该功能
|
||||||
|
# run_config_ecdn(SECRETID, SECRETKEY, my_domain, cert_id)
|
||||||
|
|
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
tencentcloud-sdk-python==3.0.302
|
||||||
|
requests==2.22.0
|
Loading…
Reference in New Issue
Block a user