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

💻 Offline Hedy for Windows #5116

Merged
merged 60 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
41be3d1
feat: add a build for offline Hedy on Windows
rix0rrr Feb 9, 2024
dd8e790
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 9, 2024
022e1e0
Document appspec file
rix0rrr Feb 9, 2024
e75c39e
Merge branch 'hedy-offline-windows' of github.com:Felienne/hedy into …
rix0rrr Feb 9, 2024
c780aaf
Fix comment
rix0rrr Feb 9, 2024
e65dd10
Upgrade workflow to use Python 3.9
rix0rrr Feb 9, 2024
f08a114
Include Node as build tool
rix0rrr Feb 9, 2024
b7e21ce
Try to fix this on Windows
rix0rrr Feb 9, 2024
d75c3a2
Different bash location on Windows
rix0rrr Feb 9, 2024
73c00c3
Print env
rix0rrr Feb 9, 2024
dd2229b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 9, 2024
cd58908
Add MSYS to path on Windows
rix0rrr Feb 9, 2024
32c777f
Merge branch 'hedy-offline-windows' of github.com:Felienne/hedy into …
rix0rrr Feb 9, 2024
f9b72a1
Remove use of bash variable
rix0rrr Feb 9, 2024
27a4a0c
Need to use ; on Windows
rix0rrr Feb 9, 2024
cd67cca
Debugging
rix0rrr Feb 9, 2024
dc954f4
Add explicit 'bash' everywhere
rix0rrr Feb 9, 2024
a05793d
Debugging bash
rix0rrr Feb 9, 2024
79838e7
Gitbash
rix0rrr Feb 9, 2024
eaf1dee
I'm desperate
rix0rrr Feb 9, 2024
610cd20
Moar logging
rix0rrr Feb 9, 2024
fd8fa87
Does this fix it?
rix0rrr Feb 9, 2024
6dcaad5
Argh it's NPX
rix0rrr Feb 9, 2024
21cce09
npx maybe like this?
rix0rrr Feb 9, 2024
5072eda
Try installing npx explicitly
rix0rrr Feb 9, 2024
e033f5a
Print PATH
rix0rrr Feb 9, 2024
8dce000
dir that node dir
rix0rrr Feb 9, 2024
607cf4a
How about this then
rix0rrr Feb 9, 2024
4bf3b3c
But npm does work doesn't it
rix0rrr Feb 9, 2024
f03ba99
.cmd
rix0rrr Feb 9, 2024
af5fcf3
npx must be invoked with .cmd
rix0rrr Feb 9, 2024
368423b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 9, 2024
f5eca0c
print environ
rix0rrr Feb 9, 2024
c57241d
aaargh
rix0rrr Feb 9, 2024
88eddae
There is no coursedata
rix0rrr Feb 9, 2024
2478130
Ignore dist
rix0rrr Feb 9, 2024
5bd141f
Load `static_babel_content.json` relative to __file__
rix0rrr Feb 9, 2024
6646770
Reduce bundle size
rix0rrr Feb 9, 2024
8fd8f94
Disable UPX
rix0rrr Feb 9, 2024
1d85088
Do file-relative path references everywhere
rix0rrr Feb 10, 2024
c774981
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 10, 2024
e001af8
Smoketest
rix0rrr Feb 10, 2024
20ba20b
Merge branch 'hedy-offline-windows' of github.com:Felienne/hedy into …
rix0rrr Feb 10, 2024
0764f26
Create a release, and move the database
rix0rrr Feb 10, 2024
e8bc75b
Build offline when deploying to prod
rix0rrr Feb 10, 2024
09d2544
Give workflow a name
rix0rrr Feb 10, 2024
294b7a4
Final pull request test
rix0rrr Feb 10, 2024
c1d5b55
Zip location
rix0rrr Feb 11, 2024
54bd1e6
use powershell then!
rix0rrr Feb 11, 2024
9b4cf02
More content directories, default invite code
rix0rrr Feb 11, 2024
a761486
Cut down on logging so it's more useful
rix0rrr Feb 11, 2024
1455806
Suppress logging, print banner
rix0rrr Feb 11, 2024
03e9210
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 11, 2024
2aa4070
Init colorama
rix0rrr Feb 12, 2024
bb0ffed
Add startup banner and README
rix0rrr Feb 12, 2024
7fa24b0
Actually add text file
rix0rrr Feb 12, 2024
db70b93
Re-phrase Windows Defender pop-up
rix0rrr Feb 12, 2024
bee6b9e
Fix escape sequences
rix0rrr Feb 12, 2024
575a4fd
Final tweaks
rix0rrr Feb 14, 2024
192c5db
Merge branch 'main' into hedy-offline-windows
mergify[bot] Feb 15, 2024
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
59 changes: 59 additions & 0 deletions .github/workflows/build-offline.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Build offline Hedy
on:
# Can be run on-demand
workflow_dispatch: {}

# Runs when 'deploy to prod' runs
workflow_run:
workflows: ["Deploy to hedy.org"]
types: [requested]
branches:
- 'main'

jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.9
uses: actions/setup-python@v4
with:
python-version: 3.9
cache: 'pip'
- name: Set up NodeJS 18
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
- name: 'Install npx'
run: npm install -g npx
- run: pip install -r requirements.txt
name: 'Python requirements'
- run: doit run _offline
- name: Smoke test the build
run: cd dist && offlinehedy/run-hedy-server --smoketest

- uses: fregante/daily-version-action@v2
name: Create tag if necessary
id: daily-version

- name: Create zip file
# Because we're on Windows
run: |
cd dist/offlinehedy && Compress-Archive -Path . -Destination ../../offlinehedy-${{ steps.daily-version.outputs.version }}.zip
- if: steps.daily-version.outputs.created
name: Create Release
uses: shogo82148/actions-create-release@v1
id: create_release
with:
tag_name: ${{ steps.daily-version.outputs.version }}
generate_release_notes: true

- name: Upload Assets
if: steps.daily-version.outputs.created
uses: shogo82148/actions-upload-release-asset@v1
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: '*.zip'
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ MANIFEST
# 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
Expand Down Expand Up @@ -179,4 +178,5 @@ grammars-Total/*.lark
.test-cache

.doit.db
.doit.db.*
.doit.db.*
dist/
56 changes: 56 additions & 0 deletions OFFLINE_README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
_ _ _
| | | | | |
| |__| | ___ __| |_ _
| __ |/ _ \/ _` | | | |
| | | | __/ (_| | |_| |
|_| |_|\___|\__,_|\__, |
__/ |
o f f l i n e |___/

Welcome to offline Hedy! You can use offline Hedy to run Hedy on your own
computer, and it can be used by anyone on the same network.


For you
=======

When you first start up offline Hedy on a Windows computer, two things can
happen:

- Windows Firewall will ask you whether or not to allow network connections.
You should click "Allow".
- Windows Defender may say that Hedy is a dangerous program and ask you whether
or not it should be run. You should click "More Info" and then "Run Anyway".

You can create a teacher account for yourself by visiting the following link
and then clicking "Create Account".

http://localhost/invite/newteacher

You can also use one of the built-in accounts, which is named "teacher1"
with password "123456".


For students
============

When Hedy starts up, it will print a web address made from numbers. Your students
should type this address into the address bar of their browser.

The address will look something like this, but it will be different on every
computer. It can also change every time your computer starts up:

http://192.168.31.13/


Upgrading to a newer version
============================

All your data and your student's data is stored in the file `database.json`
that you will see in the program's directory. When you download a newer version
of Offline Hedy it will come with its own empty database. To keep all programs
around, you can copy over the `database.json` file from the old version to the
new version of Hedy.

To keep it simple, you can also start fresh and use the newer version only for a
different class or different year.
101 changes: 93 additions & 8 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,9 +371,14 @@ def before_request_https():

Compress(app)
Commonmark(app)
parse_logger = s3_logger.S3ParseLogger.from_env_vars()
querylog.LOG_QUEUE.set_transmitter(
aws_helpers.s3_querylog_transmitter_from_env())

# We don't need to log in offline mode
if utils.is_offline_mode():
parse_logger = s3_logger.NullLogger()
else:
parse_logger = s3_logger.S3ParseLogger.from_env_vars()
querylog.LOG_QUEUE.set_transmitter(
aws_helpers.s3_querylog_transmitter_from_env())


@app.before_request
Expand Down Expand Up @@ -2710,14 +2715,91 @@ def split_at(n, xs):
return xs[:n], xs[n:]


def on_offline_mode():
"""Prepare for running in offline mode."""
# We are running in a standalone build made using pyinstaller.
# cd to the directory that has the data files, disable debug mode, and
# use port 80 (unless overridden).
# There will be a standard teacher invite code that everyone can use
# by going to `http://localhost/invite/newteacher`.
os.chdir(utils.offline_data_dir())
config['port'] = int(os.environ.get('PORT', 80))
if not os.getenv('TEACHER_INVITE_CODES'):
os.environ['TEACHER_INVITE_CODES'] = 'newteacher'
utils.set_debug_mode(False)

# Disable logging, so Werkzeug doesn't log all requests and tell users with big red
# letters they're running a non-production server.
# from werkzeug import serving
# def do_nothing(*args, **kwargs): pass
# serving.WSGIRequestHandler.log_request = do_nothing
log = logging.getLogger('werkzeug')
log.setLevel(logging.ERROR)

# Get our IP addresses so we can print a helpful hint
import socket
ip_addresses = [addr[4][0] for addr in socket.getaddrinfo(
socket.gethostname(), None, socket.AF_INET, socket.SOCK_STREAM)]
ip_addresses = [i for i in ip_addresses if i != '127.0.0.1']

from colorama import colorama_text, Fore, Back, Style
g = Fore.GREEN
lines = [
('', ''),
('', ''),
(g, r' _ _ _ '),
(g, r'| | | | | | '),
(g, r'| |__| | ___ __| |_ _ '),
(g, r'| __ |/ _ \/ _` | | | |'),
(g, r'| | | | __/ (_| | |_| |'),
(g, r'|_| |_|\___|\__,_|\__, |'),
(g, r' __/ |'),
(g, r' o f f l i n e |___/ '),
('', ''),
('', 'Use a web browser to visit the following website:'),
('', ''),
*[(Fore.BLUE, f' http://{ip}/') for ip in ip_addresses],
('', ''),
('', ''),
]
# This is necessary to make ANSI color codes work on Windows.
# Init and deinit so we don't mess with Werkzeug's use of this library later on.
with colorama_text():
for style, text in lines:
print(Back.WHITE + Fore.BLACK + ''.ljust(10) + style + text.ljust(60) + Style.RESET_ALL)

# We have this option for testing the offline build. A lot of modules read
# files upon import, and those happen before the offline build 'cd' we do
# here and need to be written to use __file__. During the offline build,
# we want to run the actual code to see that nobody added file accesses that
# crash, but we don't actually want to start the server.
smoke_test = '--smoketest' in sys.argv
if smoke_test:
sys.exit(0)


if __name__ == '__main__':
# Start the server on a developer machine. Flask is initialized in DEBUG mode, so it
# hot-reloads files. We also flip our own internal "debug mode" flag to True, so our
# own file loading routines also hot-reload.
utils.set_debug_mode(not os.getenv('NO_DEBUG_MODE'))
no_debug_mode_requested = os.getenv('NO_DEBUG_MODE')
utils.set_debug_mode(not no_debug_mode_requested)

# For local debugging, fetch all static files on every request
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = None
if utils.is_offline_mode():
on_offline_mode()

# Set some default environment variables for development mode
env_defaults = dict(
BASE_URL=f"http://localhost:{config['port']}/",
ADMIN_USER="admin",
)
for key, value in env_defaults.items():
if key not in os.environ:
os.environ[key] = value

if utils.is_debug_mode():
# For local debugging, fetch all static files on every request
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = None

# If we are running in a Python debugger, don't use flasks reload mode. It creates
# subprocesses which make debugging harder.
Expand All @@ -2732,9 +2814,12 @@ def split_at(n, xs):
start_snapshot = tracemalloc.take_snapshot()

on_server_start()
logger.debug('app starting in debug mode')
debug = utils.is_debug_mode() and not (is_in_debugger or profile_memory)
if debug:
logger.debug('app starting in debug mode')

# Threaded option enables multiple instances for multiple user access support
app.run(threaded=True, debug=not is_in_debugger and not profile_memory,
app.run(threaded=True, debug=debug,
port=config['port'], host="0.0.0.0")

# See `Procfile` for how the server is started on Heroku.
Expand Down
75 changes: 75 additions & 0 deletions app.spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#----------------------------------------------------------
# PyInstaller configuration file
#
# This file controls how we build a standalone distribution of
# Hedy that can run in environments where Internet access might
# be spotty.
#----------------------------------------------------------
# -*- mode: python ; coding: utf-8 -*-
from os import path
import sys

dirname = 'offlinehedy'
appname = 'run-hedy-server'

# Find the venv directory. We need to be able to pass this to
# pyinstaller, otherwise it will not bundle the libraries we installed
# from the venv.
venv_dir = [p for p in sys.path if 'site-packages' in p][0]


data_files = [
# Files
('README.md', '.'),
('static_babel_content.json', '.'),

# Folders
('content', 'content'),
('grammars', 'grammars'),
('grammars-Total', 'grammars-Total'),
('prefixes', 'prefixes'),
('static', 'static'),
('templates', 'templates'),
('translations', 'translations'),
]

a = Analysis(
['app.py'],
pathex=[venv_dir],
binaries=[],
datas=data_files,
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
)
pyz = PYZ(a.pure)

exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name=appname,
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
coll = COLLECT(
exe,
a.binaries,
a.datas,
strip=False,
upx=False,
upx_exclude=[],
name=dirname,
)
Loading
Loading