Note
This is still work in progress and in a very early stage I already use it, but please be aware that it isn't bulletproof yet
As I was unable to find an all-in-one solution for pushing to remote targets, I wrote one on my own.
It can push to:
- local targets (using relative or absolute paths)
- FTP(s)
- SFTP
It can also load from all of those.
Caution
Please read this carefully. This plugin deletes files in the target in case they are not present in the source. In case you want to keep e.g. config files in the target folder, you need to explicitly state that.
Because I plan to use it to deploy a lot of static websites, it does always do the following steps:
- create additional directories
- upload asset files (see
lib/createPlan:getAssetExtensions()) replacing existing ones - upload non-assets ("logic") files replacing existing ones
- delete non-asset files that aren't in the source directory
- delete asset files that aren't in the source directory
- delete unneeded directories
In my case asset files receive a hash in their filename all the time when websites are build (e.g. index.abcdef.css). With the steps listed above no real "downtime" occurs, no matter how long the upload takes, as
- old non-asset files (".html") still can refer to the old assets
- when a new non-asset file is uploaded, its new assets are alredy there
- when a old asset file is deleted, it isn't referenced by its non-assets anymore (as they already have been deleted)
All this efford to just prevent a downtime from 2 minutes when patching websites 🥲
steps:
# just demo
- name: build
image: node:22-alpine
commands:
- npm install
- npm run generate
- name: push-to-sftp
image: ghcr.io/tino-kuptz/push-to:latest
settings:
source_path: .output/public
# source_path: /drone/src/.output/public
target_path: sftp://my-public-server.com/test
# target_path: ftp://<host>:<port>/<path>
# target_path: sftp://<host>:<port>/<path>
# target_path: ftps://<host>:<port>/<path>
# target_path: . (or any other path)
target_username:
from_secret: TARGET_USERNAME
target_password:
from_secret: TARGET_PASSWORD
# dry_run: true
# log_level: debug
# dont_override_target_files: **.env
# dont_delete_target_files: *.logNote
When using in drone ci, you need to remove the prefix "plugin_" of these.
Use dry_run: true in settings, DO NOT use PLUGIN_DRY_RUN: true
Set this one:
PLUGIN_DRY_RUN=trueThe plugin won't change anything on the remote site then, it'll just pretend to do so. If you set the logging level to debug, it will tell you things it would have done.
One of the very first logging lines will tell you if dry_run was detected. It won't be logged anywhere else.
In case one needs it:
PLUGIN_LOG_LEVEL=debugCan be any of trace, debug, info, warn, error.
Default: info
You can define extensions that count as assets; e.g. in case you upload a node.js application and don't want js files to count as asset.
PLUGIN_ASSETS_EXTENSIONS=jpg,jpeg,svg,css,...See lib/createPlan, function getAssetExtensions() for what extensions count as asset per default.
THIS PLUGIN WILL DELETE EVERYTHING ON THE REMOTE SITE.
In case that's not what you wanted, there are two ways of keeping them.
Please be aware that those will only affect exiting remote files; in case you have local files matching a pattern, that don't exist on the remote site, they will get uploaded (without overriding/deleting remote files).
Using this will not delete remote files in case they are not present in the source directory.
But it will override them, when present in source.
Usage example:
PLUGIN_DONT_DELETE_TARGET_FILES=**/.env,backup/**This will keep all .env files and everything in the backup folder
This will keep the remote files and don't override them in case they are present in the source file.
It won't delete them in case the source file is present, and it won't override them in case the source file is not present.
Usage example:
PLUGIN_DONT_OVERRIDE_TARGET_FILES=**/.envThis will keep all env files present on the remote site.
In case there are local .env files that do also exist on the remote site, they won't be uploaded.
In case there are local .env files that don't exist on the remote site, they will be uploaded.
You may use * or ** only once in every path, where * does match exactly one filename/directory and ** matches 0-n files/directories.
Examples:
| Pattern | Matches | Does not match |
|---|---|---|
* |
/index.html /index.css /.env |
/directory/index.js /another/directory/.env |
** |
/index.html /assets/main.css /assets/fonts/font-awesome.woff |
N/A |
**.env |
/.env /worker/example.env /cluster/proxy/.env |
Anything other then .env files |
*.env |
/.env /example.env |
Anything other then .env files, and .env files that are not in the root directory |
worker/*.env |
/worker/.env /worker/example.env |
Anything other then .env files, and .env files that are not in the worker directory |
worker/**.env |
/worker/.env /worker/proxy/.env /worker/proxy/example.env |
Anything other then .env files, or .env files that are not in the worker directory/any subdirectories of the worker directory |
These patterns apply to both, "don't delete" and "don't override".
Please use dry_run: true the very first time and check for messages info messages (level 30) to verify if your patterns match:
{
"level":30, "time":1759005963063, "pid":1, "hostname":"11bcc8a138d9", "name":"createPlan",
"msg":"Skipping file replacement due to DONT_OVERRIDE_TARGET_FILES: config/default.yml"
}PLUGIN_SOURCE_PATH=distPLUGIN_TARGET_PATH=/drone/src/targetFTP over a unencrypted connection
PLUGIN_SOURCE_PATH=ftp://example.com/httpdocs/prod
PLUGIN_SOURCE_USERNAME=test
PLUGIN_SOURCE_PASSWORD=i.am.securePLUGIN_TARGET_PATH=ftp://example.com:21
PLUGIN_TARGET_USERNAME=test
PLUGIN_TARGET_PASSWORD=i.am.secureFTP over an encrypted connection.
Similar to ftp, except for the additional property IGNORE_SSL_TRUST (default: false).
PLUGIN_SOURCE_PATH=ftps://example.com:21/httpdocs/prod
PLUGIN_SOURCE_USERNAME=test
PLUGIN_SOURCE_PASSWORD=i.am.secure
# SOURCE_IGNORE_SSL_TRUST=falsePLUGIN_TARGET_PATH=ftps://example.com/release
PLUGIN_TARGET_USERNAME=test
PLUGIN_TARGET_PASSWORD=i.am.secure
PLUGIN_TARGET_IGNORE_SSL_TRUST=trueSSH file transfer
PLUGIN_SOURCE_PATH=sftp://example.com:22/httpdocs/prod
PLUGIN_SOURCE_USERNAME=test
PLUGIN_SOURCE_PASSWORD=i.am.securePLUGIN_TARGET_PATH=sftp://example.com
PLUGIN_TARGET_USERNAME=test
PLUGIN_TARGET_PASSWORD=i.am.securedocker build -t ghcr.io/tino-kuptz/push-to:test .Assuming there is a directory test_fs in the root folder of this project
docker run \
-v ./test_fs:/drone/src \
-e "PLUGIN_SOURCE_PATH=source" \
-e "PLUGIN_TARGET_PATH=sftp://myserver.org/test" \
-e "PLUGIN_TARGET_USERNAME=my_ssh_user" \
-e "PLUGIN_TARGET_PASSWORD=my_ssh_pass" \
-e "PLUGIN_DRY_RUN=true" \
ghcr.io/tino-kuptz/push-to:testThis will push the local directory test_fs/source to myserver.org/test