Skip to content
Open
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
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ MONEYFORWARD_PASSWORD=Passw0rd!
YNAB_ACCESS_TOKEN=abunchofcharacters
```

<a name="one-password-cli"></a>

Alternatively, you can also use something like [1Password's CLI](https://developer.1password.com/docs/cli/)
to completely avoid storing clear secrets:

Expand Down Expand Up @@ -90,6 +92,44 @@ op run --env-file=.env -- mfynab mfynab-david.yml
After checking out the repo, run `bundle install` to install dependencies.
Then, run `bin/rake test` to run the tests.

## Running MFYNAB with cron and Docker

The `docker-example/` directory contains sample files that'll help you schedule MFYNAB inside a Docker container.

See the comments in each file for more details on how it works.

First you'll want to bring your MFYNAB configuration file into this directory:

```sh
cp path_to/config.yml docker_example/
```

Then you can build the Docker image:

```sh
docker build -t mfynab docker_example/
```

Finally, you can run the Docker image. Note that you need to pass secrets as environment variables:

```sh
docker run -d \
--env YNAB_ACCESS_TOKEN=... \
--env MONEYFORWARD_USERNAME=... \
--env MONEYFORWARD_PASSWORD='...' \
--name mfynab mfynab
```

You can also use the 1Password CLI ([documented here](#one-password-cli)) for this step:

```sh
op run --env-file=.env -- sh -c 'docker run -d \
--env YNAB_ACCESS_TOKEN=$YNAB_ACCESS_TOKEN \
--env MONEYFORWARD_USERNAME=$MONEYFORWARD_USERNAME \
--env MONEYFORWARD_PASSWORD="$MONEYFORWARD_PASSWORD" \
--name mfynab mfynab'
```

## Roadmap

### Deploy/Automate
Expand Down Expand Up @@ -135,3 +175,4 @@ Previous notes:
```
- Passing logger everywhere feels weird.
- Prompt user for captcha and other account extra authentication required by Money Forward?
- One might want to run a single Docker instance for multiple users, but the current setup does not allow that easily. We'll want to bring the secret environment variables into the config file, making it possible to assign them to a given "user", and name them accordingly.
2 changes: 2 additions & 0 deletions docker_example/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
config.yml
Gemfile.lock
19 changes: 19 additions & 0 deletions docker_example/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM ruby:3.4

# Install cron and chromium
RUN apt-get update && apt-get install -y cron chromium

WORKDIR /app

# Copy Gemfile first and install gems
COPY Gemfile Gemfile.lock ./
RUN bundle install

# Copy the rest of the application (this two-step process helps with layer caching)
COPY . .

# Add cron job
RUN crontab /app/mfynab-cron

# Run the entrypoint script by default
CMD [ "/app/entrypoint.sh" ]
5 changes: 5 additions & 0 deletions docker_example/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

source "https://rubygems.org"

gem "mfynab"
7 changes: 7 additions & 0 deletions docker_example/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env sh

# Save environment variables to a file, so we can forward them to cron jobs
env >> /etc/environment

# Run cron in the foreground
cron -f
10 changes: 10 additions & 0 deletions docker_example/mfynab-cron
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Pick up environment variables that were forwarded when starting the container
SHELL=/bin/bash
BASH_ENV=/etc/environment

# See:
# https://www.reddit.com/r/docker/comments/mzqibu/how_to_correctly_run_cron_in_docker/

# Run MFYNAB at 6am, 12pm, and 9pm JST every day
# FIXME: I'd like to be able to use UTC+9 times
0 3,9,21 * * * cd /app; bundle exec mfynab config.yml >/proc/1/fd/1 2>/proc/1/fd/2
2 changes: 1 addition & 1 deletion lib/mfynab/money_forward/account_status.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def parse_html_table_row(node)
end
end

FRESHNESS_LIMIT = 24 * 60 * 60 # 1 day
FRESHNESS_LIMIT = 60 * 60 # 1 hour

attr_reader :id_hash, :name, :raw_status, :updated_at, :key, :message

Expand Down
3 changes: 3 additions & 0 deletions lib/mfynab/money_forward/session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ def with_ferrum
timeout: 30,
process_timeout: 20, # 10s was not always enough on CI (FIXME: make configurable per env)
headless: !ENV.key?("NO_HEADLESS"),
# FIXME: this was needed to be able to run Chromium headless
# within Docker as root, but I'd rather not rely on it.
browser_options: { "no-sandbox": nil },
Comment on lines +82 to +84

Copilot AI Feb 27, 2025

Copy link

Choose a reason for hiding this comment

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

Using the 'no-sandbox' option can expose security vulnerabilities. Consider conditionally enabling it only when running in Docker by checking an environment variable to better isolate production and container-specific behavior.

Suggested change
# FIXME: this was needed to be able to run Chromium headless
# within Docker as root, but I'd rather not rely on it.
browser_options: { "no-sandbox": nil },
browser_options: ENV.key?("IN_DOCKER") ? { "no-sandbox": nil } : {},
# FIXME: this was needed to be able to run Chromium headless
# within Docker as root, but I'd rather not rely on it.

Copilot uses AI. Check for mistakes.
)
user_agent = browser.default_user_agent.sub("HeadlessChrome", "Chrome")
browser.headers.add({
Expand Down