Skip to content

Commit

Permalink
Development (#3)
Browse files Browse the repository at this point in the history
Adding in the following milestone elements:

  1.  Tmux resurrect capabilities.
  2.  Env injection support.

Tmux resurrect runs by default on the host machine when the application is launched. This can be disabled with the -disabled=true flag.

Env's can now be injected, per process, using the config.yaml file.

The contents of the .env will be loaded in per application that is denoted in the yaml config. If you want the same env for multiple apps, you will need to replicate the given line, inserting the .env into the app.

Tmux resurrect runs the saving code to store the session data 1x per minute. (If not disabled)

View the README.md for more precise instructions on running the given commands
  • Loading branch information
BitlyTwiser authored Oct 11, 2024
1 parent b075ab3 commit 19c6c26
Show file tree
Hide file tree
Showing 11 changed files with 1,278 additions and 44 deletions.
11 changes: 10 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,16 @@
"request": "launch",
"name": "Debug",
"program": "${workspaceFolder}/zig-out/bin/apprunner",
"args": ["test_config.yml"],
"args": ["-restore=true"],
// "args": ["-config_path=test_config.yml"],
"cwd": "${workspaceFolder}",
},
{
"type": "lldb",
"request": "launch",
"name": "Debug test",
"program": "${workspaceFolder}/zig-out/bin/test",
"args": [],
"cwd": "${workspaceFolder}",
},
]
Expand Down
103 changes: 99 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

The premise is simple: add commands to the yml file,run the program, get windows running your stuff!

Apprunner will build N number of named Tmux windows running the commands you provide. If given a path (and not standalone), the window will be opened a that directory location.
Apprunner will build N number of named Tmux windows running the commands you provide. If given a path (and not standalone), the window will be opened at that directory location running the given command.

# Prerequisites/Setup:
1. You DO need Tmux for this: https://github.com/tmux/tmux/wiki
Expand All @@ -24,7 +24,7 @@ apps:
standalone: false
start_location: /var/log
```
3. run `./apprunner <path_to_yaml_config>`
3. run `./apprunner -config_path=<path_to_yaml_config>`
4. Terminal with your set commands will appear:
![Screenshot](/assets/screenshot1.png)
![Screenshot](/assets/screenshot2.png)
Expand All @@ -37,7 +37,102 @@ tmux new-session -s test_sesh \; rename-window -t test_sesh:0 inner_window \; se
```
This will spawn a new tmux session, create & rename the window, and run the command echoing hi to base64 encode.

# Usage Examples:
Aside from the above example, here are some examples

Standard configuration path
```
./zig-out/bin/apprunner -config_path="test_config.yml"
```
restore from Resurrect config file:
```
./zig-out/bin/apprunner -restore=true
```
Note: restore will *ony* work if you have been running the application and you are using a Tmux version > 1.9! The tmux commands that are necessary to dump the sessions do not exist prior to version 1.9.

# Environment Variables
If you desire to insert a specific set of environment variables to the tmux session at runtime you can add the `env_path` to the yaml file.
The env will be loaded *per session*. For each app that is inserted, the values will be injected for that session.
THis is done using the `-e` flag from the tmux api.

Example:
```
apps:
- name: test1
command: ping google.com
standalone: true
start_location: ./var/thing
- name: test2
command: ls -la
standalone: false
start_location: /var/log
env_path: .env
```
Note the `env_path` value. This specifies that the .env file should be co-located next to the binary.

You can also use relative paths:
```
apps:
- name: test1
command: ping google.com
standalone: true
start_location: ./var/thing
- name: test2
command: ls -la
standalone: false
start_location: /var/log
env_path: ../../.env
```

Or full absolute paths:
```
apps:
- name: test1
command: ping google.com
standalone: true
start_location: ./var/thing
- name: test2
command: ls -la
standalone: false
start_location: /var/log
env_path: /home/place/.env
```


### Tmux Resurrect
Resurrect runs automatically unless disabled with the `-disabled=true` flag.

example:
```
./apprunner -config_path="<file_path>" -disabled=true
```

This will print a warning that disabled is set and resurrect will not run.

To restore your session, just run:

```
./zig-out/bin/apprunner -restore=true
```

#### Config file storage:
Resurrect stores a config file at `$HOME/.tmux/resurrect/config.json`

If the $HOME env var is not set for some reason, then its stored at `/home/.tmux/resurrect/config.json`

The folder and file are created automatically at application runtime.

#### Important Notes:
Apprunner will track user sessions that have been created within the apprunner session. It is important to note that this does *not* aply to *all* sessions that might be running!

Apprunner *only* tracks the sessions that was spawned with Apprunner (i.e. by running the program you spawn the session). If you make windows and panes *within* your apprunner sessions, those will also be captured.

If you create a new shell and start a *new* tmux sessions outside of app runner this would *not* be captured! Only the Apprunner sessions will be caught by the resurrect code :)

If the config.json file is removed, a new file will be created at runtime.


## Roadmap:
- Multi-pane per window support. Instead of windows only, allow for user to select a split pane layout (i.e. split pane horizontal/vertical etc..)
- Save/Store runtime progress (like tmux resurrect)
- [ ] Multi-pane per window support. Instead of windows only, allow for user to select a split pane layout (i.e. split pane horizontal/vertical etc..) in the Yaml file (Note - Resurrect will capture panes by default if they are created manually in the sessions)
- [X] Save/Store runtime progress (like tmux resurrect)
- [X] Env file loading (provide an .env and have the values loaded into the application)
8 changes: 8 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ pub fn build(b: *std.Build) void {
const ymlz = b.dependency("ymlz", .{});
exe.root_module.addImport("ymlz", ymlz.module("root"));

// Add Zdotenv
const zdotenv = b.dependency("zdotenv", .{});
exe.root_module.addImport("zdotenv", zdotenv.module("zdotenv"));

// Add Snek
const snek = b.dependency("snek", .{});
exe.root_module.addImport("snek", snek.module("snek"));

const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());

Expand Down
8 changes: 8 additions & 0 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@
.url = "https://github.com/pwbh/ymlz/archive/refs/tags/0.3.4.tar.gz",
.hash = "1220ed1736bf5f543a77a7bf205d884fa5c5e9393ab99f03401018c8af5792cf8373",
},
.zdotenv = .{
.url = "https://github.com/BitlyTwiser/zdotenv/archive/refs/tags/v0.1.0.tar.gz",
.hash = "122016a556577271f89aea3e7a052abb7c9731f7478ac350e1e64b07c6620418528f",
},
.snek = .{
.url = "https://github.com/BitlyTwiser/snek/archive/refs/tags/v0.1.0.tar.gz",
.hash = "1220ca47f02a4dbfffa8b24e242b7f4d9af0690e1dc14fc2c2475d74ceeec9150ee1",
},
},
.paths = .{
"build.zig",
Expand Down
1 change: 1 addition & 0 deletions src/config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub const App = struct {
command: []const u8,
standalone: bool,
start_location: []const u8, // For specific folder, if not standalone
env_path: ?[]const u8,
};

pub const Config = struct {
Expand Down
83 changes: 64 additions & 19 deletions src/main.zig
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
const std = @import("std");
const builtin = @import("builtin");
const os = std.os;
const assert = std.debug.assert;
const config = @import("./config.zig");
const runner = @import("./runner.zig");
const resurrect = @import("./resurrect.zig").Resurrect;
const shell_err = runner.ShellError;
const snek = @import("snek").Snek;

// Functions from std
const print = std.debug.print;
const assert = std.debug.assert;

const CliArguments = struct { config_path: ?[]const u8, restore: ?bool, disable: ?bool };

pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
Expand All @@ -17,28 +20,70 @@ pub fn main() !void {
const args = try std.process.argsAlloc(allocator);
defer std.process.argsFree(allocator, args);

if (args.len < 2) {
print("Please provide a path to the config.yml file", .{});
return;
var cli = try snek(CliArguments).init(allocator);
const parsed_cli = try cli.parse();

// Check tmux version and print warning if we cannot start resurrect
const res = try resurrect.init(allocator);
const res_supported = try res.checkSupportedVersion();

const disabled = (parsed_cli.disable orelse false);

// If disabled is not set, we obviously defualt to false
if (res_supported and !disabled and parsed_cli.restore == null) {
// Start the thread to store session data every N minutes/seconds
try res.saveThread();
} else {
if (disabled) {
try res.printDisabledWarning();

std.time.sleep(std.time.ns_per_s * 3);
} else if (parsed_cli.restore == null) {
try res.printWarning();
// Sleep for 3 seconds to display the warning to the user
std.time.sleep(std.time.ns_per_s * 3);
}
}

var yml_config = try config.YamlConfig.init(allocator, args[1]);
const results = try yml_config.parseConfig();
defer allocator.free(results.apps);
// Cli application path parsing. Either restore or run the application normally using config file path
if (parsed_cli.config_path) |config_path| {
if (parsed_cli.restore != null) {
print("{s}", .{"Cannot use restore flag with config path.\n"});
return;
}

var yml_config = try config.YamlConfig.init(allocator, config_path);
const results = try yml_config.parseConfig();
defer allocator.free(results.apps);

// Spawns all threads and waits
var run = runner.Runner.init(allocator) catch |err| {
switch (err) {
runner.ShellError.ShellNotFound => {
print("error finding appropriate shell to run tmux commands. Please change shells and try again\n", .{});
},
}
return;
};
try run.spawner(results.apps);

// Listen for the exit events on ctrl+c to gracefully exit
try setAbortSignalHandler(handleAbortSignal);
} else if (parsed_cli.restore != null and parsed_cli.config_path == null) {
// Do not allow resurrection on non-supported version of tmux
if (!res_supported) {
print("Invalid version of Tmux. You must use Tmux version 1.9 or greater for resurrection", .{});

// Spawns all threads and waits
var run = runner.Runner.init(allocator) catch |err| {
switch (err) {
runner.ShellError.ShellNotFound => {
print("error finding appropriate shell to run tmux commands. Please change shells and try again", .{});
},
return;
}
return;
};
try run.spawner(results.apps);

// Listen for the exit events on ctrl+c to gracefully exit
try setAbortSignalHandler(handleAbortSignal);
// Restore stored session after crash, use a catch here and handle this error gradefully
try res.restoreSession();

try setAbortSignalHandler(handleAbortSignal);
} else {
print("Invalid commands specified, please pass either config file path or restore flag as an argument to apprunner. Use apprunner -h for help\n", .{});
}
}

// Gracefully exit on signal termination events
Expand Down
Loading

0 comments on commit 19c6c26

Please sign in to comment.