Skip to content

add compile fail if trying to update secrets #53

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

Merged
merged 4 commits into from
Jun 29, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
91 changes: 91 additions & 0 deletions extra_scripts/post.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#!/usr/bin/env python3
"""
Post-upload script for PlatformIO

This script runs after successful firmware upload and performs the following actions:
1. Renames secrets.json to secrets.json.injected to indicate successful injection
2. Preserves previous injected files with timestamps
3. Provides clear feedback about the secrets injection status

Usage:
- Automatically runs after: pio run -e <env> --target upload
- Helps track which secrets have been injected into firmware
- Prevents accidental reuse of the same secrets file

To reuse secrets: cp secrets.json.injected secrets.json
"""

import os
import json
import shutil
from datetime import datetime

Import("env")


def rename_secrets_after_upload(*args, **kwargs):
"""
Post-upload action to rename secrets.json to secrets.json.injected
This indicates that the secrets have been successfully injected into the firmware.
Adds a syntax error comment to force users to read warnings when reusing.
"""
secrets_file = "secrets.json"
injected_file = "secrets.json.injected"

if os.path.exists(secrets_file):
try:
# Add timestamp to the injected file for tracking
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
timestamped_file = f"secrets.json.injected.{timestamp}"

# If an injected file already exists, rename it with timestamp
if os.path.exists(injected_file):
print(f"Previous {injected_file} found, renaming to {timestamped_file}")
shutil.move(injected_file, timestamped_file)

# Read the original secrets file
with open(secrets_file, 'r') as f:
original_content = f.read()

# Create content with intentional JSON syntax error
# Add a trailing comma and comment that breaks JSON parsing
syntax_error_content = f'''{{
"WARNING_REMOVE_THIS_LINE_TO_USE": "If you modified secrets, ERASE DEVICE FIRST: pio run -e <env> -t erase",
// INTENTIONAL SYNTAX ERROR: Remove this comment line and the line above to use
{original_content[1:-1]},
"INJECTED_TIMESTAMP": "{timestamp}",
}}'''

# Write the content with syntax error to injected file
with open(injected_file, 'w') as f:
f.write(syntax_error_content)

# Remove the original file
os.remove(secrets_file)

print(f"✓ Successfully created {injected_file} with deployment tracking")
print(f" This indicates secrets have been injected into the firmware.")
print(f" To reuse: copy {injected_file} to {secrets_file} and remove the warning line")

except Exception as e:
print(f"Warning: Failed to process {secrets_file}: {e}")
# Fallback to simple rename if JSON processing fails
try:
shutil.move(secrets_file, injected_file)
print(f"✓ Fallback: renamed {secrets_file} to {injected_file}")
except Exception as e2:
print(f"Warning: Fallback rename also failed: {e2}")
else:
print(f"No {secrets_file} file found - nothing to rename")


def main():
"""
Main function that sets up the post-upload action
"""
# Add post-upload action to rename secrets file
env.AddPostAction("upload", rename_secrets_after_upload)


# Execute main function
main()
130 changes: 97 additions & 33 deletions extra_scripts/pre.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,83 @@
Import("env")


def load_secrets_config():
"""
Load secrets configuration based on available files.
Handles three scenarios:
1. secrets.json exists - use it (fresh deployment)
2. secrets.json.injected exists but secrets.json doesn't - inform user
3. Neither exists - use placeholder values
"""
secrets_file = "secrets.json"
injected_file = "secrets.json.injected"

secrets_exist = os.path.exists(secrets_file)
injected_exist = os.path.exists(injected_file)

if secrets_exist:
# Scenario 1: secrets.json exists - use it for fresh deployment
print("=" * 60)
print("📄 Found secrets.json - Loading configuration for deployment")
if injected_exist:
print("⚠️ WARNING: Both secrets.json and secrets.json.injected exist!")
print(" This suggests you may be re-deploying with new secrets.")
print(" If you modified secrets, you MUST erase the device first:")
print(" pio run -e <your-env> -t erase")
print("=" * 60)

try:
with open(secrets_file, "r") as f:
json_config = json.load(f)
return {
"wifi_ssid": json_config.get("WIFI_SSID", ""),
"wifi_password": json_config.get("WIFI_PASSWORD", ""),
"remote_url": json_config.get("REMOTE_URL", ""),
"refresh_interval_seconds": json_config.get("REFRESH_INTERVAL_SECONDS", 10),
"default_brightness": json_config.get("DEFAULT_BRIGHTNESS", 10),
"source": "secrets.json"
}
except (json.JSONDecodeError, IOError) as e:
print(f"❌ Error reading secrets.json: {e}")
print(" Build failed - cannot proceed without valid secrets configuration.")
exit(1)

elif injected_exist:
# Scenario 2: Only secrets.json.injected exists - use placeholders
print("=" * 60)
print("📋 Found secrets.json.injected but no secrets.json")
print(" The injected file indicates secrets were previously deployed.")
print("")
print(" To deploy with NEW secrets:")
print(" 1. Copy: cp secrets.json.injected secrets.json")
print(" 2. Edit secrets.json and fix any syntax errors")
print(" 3. If you modified secrets, run: pio run -e <your-env> -t erase")
print(" 4. Run: pio run -e <your-env> --target upload")
print("=" * 60)

else:
# Scenario 3: Neither file exists - first time setup
print("=" * 60)
print("🆕 No secrets files found - First time setup")
print(" Using PLACEHOLDER values for firmware compilation.")
print("")
print(" To deploy with real secrets:")
print(" 1. Copy: cp secrets.json.example secrets.json")
print(" 2. Edit secrets.json with your actual values")
print(" 3. Run: pio run -e <your-env> --target upload")
print("=" * 60)

# Return placeholder values for scenarios 2 and 3
return {
"wifi_ssid": "XplaceholderWIFISSID____________",
"wifi_password": "XplaceholderWIFIPASSWORD________________________________________",
"remote_url": "XplaceholderREMOTEURL___________________________________________________________________________________________________________",
"refresh_interval_seconds": 10,
"default_brightness": 30,
"source": "placeholder"
}


def main() -> None:
# copy libwebp's library.json to the lib directory
env.Execute(Copy("$PROJECT_LIBDEPS_DIR/$PIOENV/libwebp/library.json", "$PROJECT_DIR/lib/webp/library.json"))
Expand All @@ -15,44 +92,31 @@ def main() -> None:
print(f"Deleting existing {sdkconfig_path} to force regeneration...")
os.remove(sdkconfig_path)

# if secrets.h file exists
if os.path.exists("secrets.json"):
# read secrets.h file
with open("secrets.json", "r") as f:
json_config = json.load(f)

wifi_ssid = json_config.get("WIFI_SSID", "")
wifi_password = json_config.get("WIFI_PASSWORD", "")
remote_url = json_config.get("REMOTE_URL", "")
refresh_interval_seconds = json_config.get(
"REFRESH_INTERVAL_SECONDS", 10
)
default_brightness = json_config.get("DEFAULT_BRIGHTNESS", 10)

else: # use environment variables
print(
"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\nWARNING : edit secrets.json.example and save as secrets.json\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
)
print("Using Xplaceholder values for direct firmware.bin modification.")
wifi_ssid = "XplaceholderWIFISSID____________"
wifi_password = "XplaceholderWIFIPASSWORD________________________________________"
remote_url = "XplaceholderREMOTEURL___________________________________________________________________________________________________________"
refresh_interval_seconds = (
10 # int(os.environ.get("REFRESH_INTERVAL_SECONDS"))
)
default_brightness = (
30 # int(os.environ.get("DEFAULT_BRIGHTNESS"))
)
# Load secrets configuration based on available files
config = load_secrets_config()

# Apply configuration to build flags
env.Append(
CCFLAGS=[
f"-DWIFI_SSID={env.StringifyMacro(wifi_ssid)}",
f"-DWIFI_PASSWORD={env.StringifyMacro(wifi_password)}",
f"-DREMOTE_URL={env.StringifyMacro(remote_url)}",
f"-DREFRESH_INTERVAL_SECONDS={refresh_interval_seconds}",
f"-DDEFAULT_BRIGHTNESS={default_brightness}",
f"-DWIFI_SSID={env.StringifyMacro(config['wifi_ssid'])}",
f"-DWIFI_PASSWORD={env.StringifyMacro(config['wifi_password'])}",
f"-DREMOTE_URL={env.StringifyMacro(config['remote_url'])}",
f"-DREFRESH_INTERVAL_SECONDS={config['refresh_interval_seconds']}",
f"-DDEFAULT_BRIGHTNESS={config['default_brightness']}",
],
)

# Print final configuration summary
print(f"🔧 Build configuration loaded from: {config['source']}")
if config['source'] != 'placeholder':
print(f" SSID: {config['wifi_ssid']}")
print(f" URL: {config['remote_url']}")
print(f" Refresh: {config['refresh_interval_seconds']}s")
print(f" Brightness: {config['default_brightness']}")
if config['source'] == 'secrets.json.injected':
print(" ℹ️ Using previously injected configuration")
else:
print(" Using placeholder values - firmware will need manual configuration")


main()
3 changes: 2 additions & 1 deletion platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ framework = espidf
monitor_speed = 115200
; monitor_rts = 0
; monitor_dtr = 0
extra_scripts =
extra_scripts =
pre:extra_scripts/pre.py
post:extra_scripts/post.py
extra_scripts/reset.py
monitor_filters =
direct
Expand Down
4 changes: 2 additions & 2 deletions secrets.json.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"WIFI_SSID": "myssiD",
"WIFI_PASSWORD": "PASSWORD",
"REMOTE_URL": "http://192.168.1.10:8000/admin/tronbyt_1/next",
"REMOTE_URL": "http://192.168.1.10:8000/ababababab/next",
"REFRESH_INTERVAL_SECONDS": 10,
"DEFAULT_BRIGHTNESS" : 30
"DEFAULT_BRIGHTNESS" : 30,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The trailing comma after the DEFAULT_BRIGHTNESS value makes this file invalid JSON, which will raise a json.JSONDecodeError if a user copies this example file to secrets.json without editing. Please remove the trailing comma to ensure it's valid JSON.

    "DEFAULT_BRIGHTNESS" : 30

}