Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add initialization container to import a save file from a url #40

Merged
merged 8 commits into from
Jul 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion charts/factorio-server-charts/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ sources:
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 1.2.3
version: 1.2.4

# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
Expand Down
30 changes: 30 additions & 0 deletions charts/factorio-server-charts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,32 @@ serverPassword:
# Existing Secret containing a `game_password` data entry
passwordSecret: ''
```
## Importing a save file

> :warning: Importing a save file will **OVERWRITE THE SERVER SAVEFILE** with the name specified in `factorioServer.save_name`. Import with caution!

### Importing by URL

To import your save file from a URL, set `import_save.source_url` to a direct-download link for your savegame. By default, the file will be downloaded and imported only once.

If, on pod intialization, you wish to re-import the file every time the contents of the savegame change, set `import_save.reimport_on_change` to `true`.
:warning: If the savegame at the source url changes, this will overwrite the server save with that file. Use with caution!

If you wish to reimport the save file every time the pod reinitializes, regardless of changes, set `import_save.reimport_every_time` to `true`. This could be useful for demos or testing.
:warning: This will overwrite the server savegame **every time the pod reinitializes**. Use with caution!

### Manual Import

To import an existing save file, start/restart the pod at least once. This will create the import folder structure.

Now, copy the savegame you wish to import to the `/factorio/save-importer/import/<existing_savegame_name>.zip` on the running pod using whatever mechanism you prefer. To do this with kubectl:

```bash
kubectl cp ./my_existing_savegame.zip <namespace>/<pod_name>:/factorio/save-importer/import
```

Restart the pod again to import your save file.


## Installing mods

Expand Down Expand Up @@ -221,6 +247,10 @@ If you do run into any issues with mods, I will try to work with you on finding
| `factorioServer.generate_new_save` | Generate a new save if `save_name` is not found | `true` |
| `factorioServer.update_mods_on_start` | Update mods on server start | `false` |
| `factorioServer.load_latest_save` | Lets the game know if you want to load the latest save | `true` |
| `import_save.enabled` | Enable save importer. Importer runs at every pod initialization. **:warning: Overwrites existing save if successful** | `true` |
| `import_save.source_url` | Full URL (including http(s)://). If left blank, saves can still be imported by placing in '/factorio/save-importer/import' | `""` |
| `import_save.reimport_on_change` | Reimport save file from source_url when checksum changes. File will be downloaded at every pod initialization. | `false` |
| `import_save.reimport_every_time` | Reimport save file from source_url at every pod intialization. Useful for resetting demos or testing. | `false` |
| `account.accountSecret` | Existing secret containing a valid factorio.com username and either a password or a token (or both) | `""` |
| `account.username` | Factorio.com username, ignored if `account.accountSecret` is set | `""` |
| `account.password` | Factorio.com password, ignored if `account.accountSecret` is set | `""` |
Expand Down
2 changes: 1 addition & 1 deletion charts/factorio-server-charts/doc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ repository:
name: github
chart:
name: factorio-server-charts
version: 1.0.3
version: 1.2.4
values: "-- generate from values file --"
valuesExample: "-- generate from values file --"
release:
Expand Down
24 changes: 23 additions & 1 deletion charts/factorio-server-charts/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,26 @@ spec:
- name: account-data
mountPath: /account
{{- end }}
{{- end }}
{{- end }}
{{- if .Values.import_save.enabled }}
- name: import-factorio-save
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
command:
- /bin/bash
- -ec
- |
bash /scripts/save-importer.sh
{{- with .Values.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
volumeMounts:
- name: datadir
mountPath: /factorio
- name: {{ template "factorio-server-charts.fullname" . }}-save-importer-configmap
mountPath: /scripts
{{- end }}
containers:
- name: {{ template "factorio-server-charts.fullname" . }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
Expand Down Expand Up @@ -195,3 +214,6 @@ spec:
- name: {{ template "factorio-server-charts.fullname" . }}-mod-downloader-configmap
configMap:
name: {{ template "factorio-server-charts.fullname" . }}-mod-downloader-configmap
- name: {{ template "factorio-server-charts.fullname" . }}-save-importer-configmap
configMap:
name: {{ template "factorio-server-charts.fullname" . }}-save-importer-configmap
207 changes: 207 additions & 0 deletions charts/factorio-server-charts/templates/save-importer-configmap.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ template "factorio-server-charts.fullname" . }}-save-importer-configmap
labels:
app: {{ template "factorio-server-charts.fullname" . }}
chart: {{ template "factorio-server-charts.fullname" . }}
release: "{{ .Release.Name }}"
heritage: "{{ .Release.Service }}"
data:
save-importer.sh: |
#directory where file at $source_url will be downloaded to
download_dir="/factorio/save-importer/download"

#directory where files from $source_url that meet staging criteria will be downloaded to
#OR where files can be manually placed for import regardless of criteria.
staging_dir="/factorio/save-importer/import"

#file used to record checksums and urls of previously downloaded files
import_data="/factorio/save-importer/import_data.txt"

target_dir="/factorio/saves"
app_name="factorio"
file_extension=".zip" #note, this is used both for searching the staging_dir and displaying messages to the user
file_type="save"

#config read from values.yaml
target_filename="{{ .Values.factorioServer.save_name }}.zip" #name for the downloaded file
source_url="{{ .Values.import_save.source_url }}"
reimport_on_file_change={{ .Values.import_save.reimport_on_change | int }}
reimport_every_time={{ .Values.import_save.reimport_every_time | int }}


main() {
echo "starting $file_type import process"

build_import_structure

if [ "${#source_url}" -gt 0 ]; then
echo "source_url $source_url provided. checking..."
download_file
stage_downloaded_file
else
echo "no download url specified. checking $staging_dir for manually staged files..."
fi

import_file
}

get_sha256sum() {
sha256sum "$1" | awk '{ print $1 }'
}

get_previous_sha256sum() {
echo $(grep "^${source_url}::" "$import_data" | awk -F '::' '{print $NF}')
}

build_import_structure() {
# staging_dir
if [ ! -d "$staging_dir" ]; then
mkdir -p "$staging_dir"
fi

# download_dir
if [ ! -d "$download_dir" ]; then
mkdir -p "$download_dir"
fi

# target_dir
if [ ! -d "$target_dir" ]; then
mkdir -p "$target_dir"
fi

# data file
if [ ! -f "$import_data" ]; then
touch "$import_data"
fi
}

download_file() {
do_download=0
if [ "$reimport_every_time" -eq 1 ]; then
do_download=1
echo "reimport_every_time is set to true."
else
echo "reimport_every_time is set to false."
fi

if [ "$reimport_on_file_change" -eq 1 ]; then
do_download=1
echo "reimport_on_file_change is set to true."
else
echo "reimport_on_file_change is set to false."
fi

if ! grep -q "$source_url" "$import_data"; then
do_download=1
echo "source '$source_url' not previously downloaded."
else
echo "source '$source_url' previously downloaded."
fi

if [ "$do_download" -eq 1 ]; then
echo "downloading '$source_url'..."
if curl -L -o "$download_dir/$target_filename" "$source_url"; then
echo "$file_type file downloaded from '$source_url' and renamed '$target_filename'"
else
echo "unable to download $file_type file from '$source_url'. skipping import process."
exit 0
fi
else
echo "conditions not met to download file."
fi
}



write_sha256sum () {
echo "writing checksum of '$source_url' download to '$import_data' file for future runs of the import script."
if grep -q "^${source_url}::" "$import_data"; then
# Update the checksum if the file entry already exists (escape any ampersands!)
sed -i "s~^${source_url}::.*~${source_url//&/\\&}::${checksum}~" "$import_data"
else
# Append the new entry to the checksum file if it doesn't exist
echo "${source_url}::${checksum}" >> "$import_data"
fi
}


stage_downloaded_file(){
stage_file=0
if [ -e "$download_dir/$target_filename" ]; then
#get checksum of file, and any previous checksums that might exist for this source url
checksum=$(get_sha256sum "$download_dir/$target_filename")
previous_checksum=$(get_previous_sha256sum "$source_url")
echo "previous checksum: $previous_checksum"

if [ "$reimport_every_time" -eq 1 ]; then
stage_file=1
echo "reimport_every_time flag is set. file will be staged for import"
fi

if [ -z "$previous_checksum" ]; then
stage_file=1
echo "no record found of a previous download for this file. file will be staged for import."
fi

if [ "$checksum" != "$previous_checksum" -a "$reimport_on_file_change" ]; then
echo "file from '$source_url' has changed since we last downloaded it..."
if [ "$reimport_on_file_change" -eq 1 ]; then
stage_file=1
echo "...and 'reimport_on_file_change' is enabled. file will be staged for import"
else
echo "...but 'reimport_on_file_change' is disabled. file will NOT be staged for import"
fi
else
echo "file from '$source_url' has NOT changed since we last downloaded it..."
fi

if [ "$stage_file" -eq 1 ]; then
echo "file downloaded from $source_url meets conditions for import. Moving to $staging_dir to prepare for $file_type import."
write_sha256sum
mv -f "$download_dir/$target_filename" "$staging_dir"
else
echo "file downloaded from $source_url does not meet conditions for import. Deleting the downloaded file."
rm -f "$download_dir/$target_filename"
fi
else
echo "target file not found in download directory. checking $staging_dir for manually staged files."
fi
}

import_file() {
# Count the number of files with the $file_extension in the source dir
count=$(find "$staging_dir" -maxdepth 1 -type f -name "*$file_extension" | wc -l)

if [ "$count" -eq 1 ]; then
file_to_import=$(find "$staging_dir" -maxdepth 1 -type f -name "*$file_extension")
echo "Found $file_type file to import - '$file_to_import'."
echo "Copying '$file_to_import' to '$target_dir/$target_filename'. This will replace any previously existing file at this destination."
# Copy and rename the file
cp -f "$file_to_import" "$target_dir/$target_filename"
if [ $? -eq 0 ]; then
# Copy was successful
echo "File copied to '$target_dir/$target_filename'."

# Touch the new copy to be _certain_ it's the latest file
touch "$target_dir/$target_filename"

# Delete the original file, so we don't reimport it again
rm "$file_to_import"
echo "staging file '$file_to_import' deleted."
else
echo "Error copying the file."
exit 1
fi
elif [ "$count" -eq 0 ]; then
echo "No $file_type file found in '$staging_dir'"
echo "Skipping $file_type import process."
else
echo "Multiple $file_type files found in '$staging_dir'"
echo "Put only one $app_name $file_type $file_extension file in '$staging_dir' to enable the import process."
echo "Skipping $file_type import process."
fi
}

main
11 changes: 11 additions & 0 deletions charts/factorio-server-charts/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,17 @@ factorioServer:
# lets the game know if you want to load the latest save
load_latest_save: true

import_save:
# enable save importer
enabled: true
# url to download save from (http or https only).
# if no url is specified, saves can still be manually imported by placing the file in "/factorio/save-importer/import"
source_url: ""
# reimport from the source_url if the checksum of the file changes
reimport_on_change: false
# reimport from the source_url AND OVERWRITE PREVIOUS SAVE every time pod is initialized. good for demos or testing
reimport_every_time: false

## @param account.accountSecret Existing secret containing a valid factorio.com username and either a password or a token (or both)
## @param account.username Factorio.com username, ignored if `account.accountSecret` is set
## @param account.password Factorio.com password, ignored if `account.accountSecret` is set
Expand Down
Loading