@kirschbaum-development/sst-laravel 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +5 -0
- package/.editorconfig +18 -0
- package/Dockerfile.web +57 -0
- package/Dockerfile.worker +85 -0
- package/README.md +268 -0
- package/conf/entrypoint.d/99-example.sh +2 -0
- package/conf/etc/s6-overlay/s6-rc.d/laravel-horizon/finish +4 -0
- package/conf/etc/s6-overlay/s6-rc.d/laravel-horizon/laravel-horizon +16 -0
- package/conf/etc/s6-overlay/s6-rc.d/laravel-horizon/run +2 -0
- package/conf/etc/s6-overlay/s6-rc.d/laravel-horizon/type +1 -0
- package/conf/etc/s6-overlay/s6-rc.d/laravel-scheduler/finish +4 -0
- package/conf/etc/s6-overlay/s6-rc.d/laravel-scheduler/laravel-scheduler +16 -0
- package/conf/etc/s6-overlay/s6-rc.d/laravel-scheduler/run +2 -0
- package/conf/etc/s6-overlay/s6-rc.d/laravel-scheduler/type +1 -0
- package/conf/usr/local/bin/entrypoint.sh +76 -0
- package/conf/usr/local/bin/s6-install.sh +33 -0
- package/images/diagram.png +0 -0
- package/laravel-sst.ts +500 -0
- package/package.json +26 -0
- package/src/laravel-env.ts +154 -0
- package/sst-env.d.ts +9 -0
package/.dockerignore
ADDED
package/.editorconfig
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
root = true
|
|
2
|
+
|
|
3
|
+
[*]
|
|
4
|
+
charset = utf-8
|
|
5
|
+
end_of_line = lf
|
|
6
|
+
indent_size = 2
|
|
7
|
+
indent_style = space
|
|
8
|
+
insert_final_newline = true
|
|
9
|
+
trim_trailing_whitespace = true
|
|
10
|
+
|
|
11
|
+
[*.md]
|
|
12
|
+
trim_trailing_whitespace = false
|
|
13
|
+
|
|
14
|
+
[*.{yml,yaml}]
|
|
15
|
+
indent_size = 2
|
|
16
|
+
|
|
17
|
+
[docker-compose.yml]
|
|
18
|
+
indent_size = 4
|
package/Dockerfile.web
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
ARG PHP_VERSION=8.3
|
|
2
|
+
FROM serversideup/php:${PHP_VERSION}-unit as base
|
|
3
|
+
|
|
4
|
+
USER root
|
|
5
|
+
|
|
6
|
+
LABEL authors="Kirschbaum"
|
|
7
|
+
LABEL maintainer="Kirschbaum"
|
|
8
|
+
|
|
9
|
+
ENV AUTORUN_ENABLED=false
|
|
10
|
+
# ENV AUTORUN_LARAVEL_MIGRATION=true
|
|
11
|
+
|
|
12
|
+
RUN apt-get update \
|
|
13
|
+
&& apt-get upgrade -y
|
|
14
|
+
|
|
15
|
+
RUN install-php-extensions gd intl
|
|
16
|
+
|
|
17
|
+
# ENV NVM_VERSION v0.39.7
|
|
18
|
+
# ENV NODE_VERSION 21.6.0
|
|
19
|
+
# ENV NVM_DIR /usr/local/nvm
|
|
20
|
+
# RUN mkdir "$NVM_DIR"
|
|
21
|
+
|
|
22
|
+
# RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
|
|
23
|
+
|
|
24
|
+
# ENV NODE_PATH $NVM_DIR/v$NODE_VERSION/lib/node_modules
|
|
25
|
+
# ENV PATH $NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH
|
|
26
|
+
|
|
27
|
+
# RUN echo "source $NVM_DIR/nvm.sh \
|
|
28
|
+
# && nvm install $NODE_VERSION \
|
|
29
|
+
# && nvm alias default $NODE_VERSION \
|
|
30
|
+
# && nvm use default \
|
|
31
|
+
# && nvm install-latest-npm" | bash
|
|
32
|
+
|
|
33
|
+
FROM base as development
|
|
34
|
+
|
|
35
|
+
# Save the build arguments as a variable
|
|
36
|
+
ARG USER_ID
|
|
37
|
+
ARG GROUP_ID
|
|
38
|
+
|
|
39
|
+
# Drop back to our unprivileged user
|
|
40
|
+
USER www-data
|
|
41
|
+
|
|
42
|
+
FROM base as deploy
|
|
43
|
+
|
|
44
|
+
ARG ENV_FILENAME
|
|
45
|
+
ENV CONTAINER_TYPE=web
|
|
46
|
+
|
|
47
|
+
# Enable OPCache
|
|
48
|
+
ENV PHP_OPCACHE_ENABLE=1
|
|
49
|
+
|
|
50
|
+
COPY --chown=www-data:www-data . /var/www/html
|
|
51
|
+
COPY --chown=www-data:www-data $ENV_FILENAME /var/www/html/.env
|
|
52
|
+
|
|
53
|
+
# Copy deploy script generated into .sst/laravel by the build step
|
|
54
|
+
COPY --chmod=755 .sst/laravel/deploy/60-deploy.sh /etc/entrypoint.d/60-deploy.sh
|
|
55
|
+
|
|
56
|
+
# Drop back to our unprivileged user
|
|
57
|
+
USER www-data
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
ARG PHP_VERSION=8.3
|
|
2
|
+
FROM serversideup/php:${PHP_VERSION}-cli AS phpcli
|
|
3
|
+
|
|
4
|
+
##########
|
|
5
|
+
# S6 Build
|
|
6
|
+
##########
|
|
7
|
+
FROM phpcli AS s6-build
|
|
8
|
+
|
|
9
|
+
ARG CONF_PATH
|
|
10
|
+
|
|
11
|
+
USER root
|
|
12
|
+
|
|
13
|
+
ARG S6_DIR='/opt/s6/'
|
|
14
|
+
ARG S6_SRC_URL="https://github.com/just-containers/s6-overlay/releases/download"
|
|
15
|
+
|
|
16
|
+
# copy our scripts
|
|
17
|
+
COPY --chmod=755 $CONF_PATH /
|
|
18
|
+
|
|
19
|
+
RUN s6-install.sh
|
|
20
|
+
|
|
21
|
+
############
|
|
22
|
+
# BASE IMAGE
|
|
23
|
+
############
|
|
24
|
+
FROM phpcli AS base
|
|
25
|
+
|
|
26
|
+
ARG CONF_PATH
|
|
27
|
+
ARG CUSTOM_CONF_PATH
|
|
28
|
+
|
|
29
|
+
USER root
|
|
30
|
+
|
|
31
|
+
LABEL authors="Kirschbaum"
|
|
32
|
+
LABEL maintainer="Kirschbaum"
|
|
33
|
+
|
|
34
|
+
ENV AUTORUN_ENABLED=false
|
|
35
|
+
ENV AUTORUN_LARAVEL_MIGRATION=false
|
|
36
|
+
|
|
37
|
+
# copy our scripts
|
|
38
|
+
RUN test -n "$CUSTOM_CONF_PATH"
|
|
39
|
+
|
|
40
|
+
COPY --chmod=755 $CONF_PATH /
|
|
41
|
+
COPY --chmod=755 $CUSTOM_CONF_PATH /
|
|
42
|
+
|
|
43
|
+
# copy s6-overlay from s6-build
|
|
44
|
+
COPY --from=s6-build /opt/s6/ /
|
|
45
|
+
|
|
46
|
+
RUN apt-get update \
|
|
47
|
+
&& apt-get upgrade -y
|
|
48
|
+
|
|
49
|
+
# RUN install-php-extensions gd intl
|
|
50
|
+
|
|
51
|
+
###################
|
|
52
|
+
# Development image
|
|
53
|
+
###################
|
|
54
|
+
FROM base AS development
|
|
55
|
+
|
|
56
|
+
# Save the build arguments as a variable
|
|
57
|
+
ARG USER_ID
|
|
58
|
+
ARG GROUP_ID
|
|
59
|
+
|
|
60
|
+
# Drop back to our unprivileged user
|
|
61
|
+
USER www-data
|
|
62
|
+
|
|
63
|
+
##############
|
|
64
|
+
# Deploy image
|
|
65
|
+
##############
|
|
66
|
+
FROM base AS deploy
|
|
67
|
+
|
|
68
|
+
ENV CONTAINER_TYPE=cli
|
|
69
|
+
|
|
70
|
+
COPY --chown=www-data:www-data . /var/www/html
|
|
71
|
+
|
|
72
|
+
# Fix S6 Overlay issues with Big Cloud PaaS (https://github.com/serversideup/docker-php/pull/376#issuecomment-2179262427)
|
|
73
|
+
RUN chown -R www-data:www-data /run
|
|
74
|
+
|
|
75
|
+
# Copy deploy script generated into .sst/laravel by the build step
|
|
76
|
+
COPY --chmod=755 .sst/laravel/deploy/60-deploy.sh /etc/entrypoint.d/60-deploy.sh
|
|
77
|
+
|
|
78
|
+
USER www-data
|
|
79
|
+
|
|
80
|
+
ENTRYPOINT ["entrypoint.sh"]
|
|
81
|
+
|
|
82
|
+
# Set stop signal to SIGQUIT for a graceful shutdown instead of S6's preferred SIGTERM (https://github.com/just-containers/s6-overlay/issues/586)
|
|
83
|
+
STOPSIGNAL SIGQUIT
|
|
84
|
+
|
|
85
|
+
CMD ["/init"]
|
package/README.md
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# SST Laravel
|
|
2
|
+
|
|
3
|
+
This is an unofficial extension of SST to deploy your Laravel application to AWS behind a robust, reliable and scalable infrastructure, with all the power the SST provides.
|
|
4
|
+
|
|
5
|
+
**TODO: Add explanation about what exactly SST is.**
|
|
6
|
+
|
|
7
|
+
## What it deploys
|
|
8
|
+
|
|
9
|
+
Behind the scenes, this extension uses the SST Cluster + Service component, which runs in AWS Fargate using pre-built Docker containers. This all gets deployed on your own AWS account, and you have full control over the infrastructure. Behind the scenes, we use the powerful PHP containers from Serverside Up.
|
|
10
|
+
|
|
11
|
+
This package deploys a full-blown infrastructure in AWS, with zero downtime deployments, as it can be seeing in the image below.
|
|
12
|
+
|
|
13
|
+

|
|
14
|
+
|
|
15
|
+
## Pre-requisites
|
|
16
|
+
|
|
17
|
+
1. NodeJS.
|
|
18
|
+
1. Have [SST](https://sst.dev) installed and configured.
|
|
19
|
+
|
|
20
|
+
## Installation instructions
|
|
21
|
+
|
|
22
|
+
Pull in the package using npm:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install @kirschbaum/sst-laravel --save
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
To start using, you only need to import the component in your `sst.config.ts` file:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { Laravel } from "@kirschbaum-development/sst-laravel";
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
And now you can start using the `Laravel` SST component. All the configuration options are Typescript files with documentation, so
|
|
37
|
+
|
|
38
|
+
// TODO: Add full list of options (auto generate?)
|
|
39
|
+
To check the full list of options. check [here]().
|
|
40
|
+
|
|
41
|
+
### HTTP
|
|
42
|
+
|
|
43
|
+
Setting up your app to receive HTTP requests, on the `laravel-sst-demo.kdg.dev` domain (with SSL), with auto-scaling with a max of 3 servers.
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
const app = new Laravel('MyLaravelApp', {
|
|
47
|
+
web: {
|
|
48
|
+
domain: 'laravel-sst-demo.kdg.dev',
|
|
49
|
+
scaling: {
|
|
50
|
+
min: 1,
|
|
51
|
+
max: 3,
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Workers
|
|
58
|
+
|
|
59
|
+
Beyond HTTP requests, you can set up one or more `workers` for your Laravel application. Workers are meant to run background commands like Laravel Horizon, the Laravel Scheduler or any background command you may need to run.
|
|
60
|
+
|
|
61
|
+
SST Laravel will automatically deploy and configure worker containers running your configured commands. See some examples below.
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
**Running the Laravel scheduler**
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
const app = new Laravel('MyLaravelApp', {
|
|
68
|
+
workers: [
|
|
69
|
+
{
|
|
70
|
+
name: 'scheduler',
|
|
71
|
+
scheduler: true,
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Running the Laravel Horizon**
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
const app = new Laravel('MyLaravelApp', {
|
|
81
|
+
workers: [
|
|
82
|
+
{
|
|
83
|
+
name: 'horizon',
|
|
84
|
+
horizon: true,
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Running custom commands**
|
|
91
|
+
|
|
92
|
+
```js
|
|
93
|
+
const app = new Laravel('MyLaravelApp', {
|
|
94
|
+
workers: [
|
|
95
|
+
{
|
|
96
|
+
name: 'worker',
|
|
97
|
+
tasks: {
|
|
98
|
+
'scheduler': {
|
|
99
|
+
command: 'php artisan schedule:work',
|
|
100
|
+
},
|
|
101
|
+
'queue': {
|
|
102
|
+
command: 'php artisan queue:work',
|
|
103
|
+
},
|
|
104
|
+
'pulse': {
|
|
105
|
+
command: 'php artisan pulse:work',
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Environment Variables
|
|
114
|
+
|
|
115
|
+
There are multiple ways to configure environment variables. If you want SST Laravel to copy an environment file, you can configure the `config.environment.file` entry.
|
|
116
|
+
|
|
117
|
+
The below configuration would copy a file named `.env.$STAGE` into the deployment containers as your `.env` file.
|
|
118
|
+
|
|
119
|
+
```js
|
|
120
|
+
const app = new Laravel('MyLaravelApp', {
|
|
121
|
+
// ...
|
|
122
|
+
config: {
|
|
123
|
+
environment: {
|
|
124
|
+
file: `.env.${$app.stage}`,
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
You can also configure it to use simply `.env`.
|
|
131
|
+
|
|
132
|
+
```js
|
|
133
|
+
const app = new Laravel('MyLaravelApp', {
|
|
134
|
+
// ...
|
|
135
|
+
config: {
|
|
136
|
+
environment: {
|
|
137
|
+
file: `.env`,
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Resources
|
|
144
|
+
|
|
145
|
+
In SST, you can [link resources](https://sst.dev/docs/linking). If you link resources to your Laravel component, SST Laravel will automatically inject and configure environment variables using sensible defaults for all the linked resources.
|
|
146
|
+
|
|
147
|
+
In the example configuration below, SST Laravel will automatically inject environment variables for the database, cache and filesystem.
|
|
148
|
+
|
|
149
|
+
```js
|
|
150
|
+
const database = new sst.aws.Postgres('MyDatabase', { vpc });
|
|
151
|
+
const redis = new sst.aws.Redis("MyRedis", { vpc });
|
|
152
|
+
const bucket = new sst.aws.Bucket("MyBucket");
|
|
153
|
+
|
|
154
|
+
const app = new Laravel('MyLaravelApp', {
|
|
155
|
+
link: [database, redis, bucket],
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The `DB_*`, `REDIS_*` and `AWS_*` environment variables will be automatically injected into your Laravel application.
|
|
160
|
+
|
|
161
|
+
#### Custom Environment Key Names
|
|
162
|
+
|
|
163
|
+
If you need to customize the environment variable names for your resources, you can provide an object with the resource and a callback function in the `link` array:
|
|
164
|
+
|
|
165
|
+
```js
|
|
166
|
+
const app = new Laravel('MyLaravelApp', {
|
|
167
|
+
link: [
|
|
168
|
+
email,
|
|
169
|
+
{
|
|
170
|
+
resource: database,
|
|
171
|
+
environment: (database: sst.aws.Postgres) => ({
|
|
172
|
+
CUSTOM_DB_HOST: database.host.apply(host => host.toString()),
|
|
173
|
+
CUSTOM_DB_NAME: database.database.apply(database => database.toString()),
|
|
174
|
+
CUSTOM_DB_USER: database.username.apply(username => username.toString()),
|
|
175
|
+
CUSTOM_DB_PASSWORD: database.password.apply(password => password.toString()),
|
|
176
|
+
})
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
resource: redis,
|
|
180
|
+
environment: (redis: sst.aws.Redis) => ({
|
|
181
|
+
QUEUE_CONNECTION: 'redis',
|
|
182
|
+
QUEUE_REDIS_HOST: redis.host.apply(host => host ? `tls://${host}` : ''),
|
|
183
|
+
QUEUE_REDIS_PORT: redis.port.apply(port => port.toString()),
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
],
|
|
187
|
+
web: {}
|
|
188
|
+
});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
The callback function receives the resource as a parameter and should return an object with the custom environment variables. The default environment variables are still set, so you can either override them or add new ones.
|
|
192
|
+
|
|
193
|
+
#### Disabling the auto-inject of environment variables
|
|
194
|
+
|
|
195
|
+
If you don't want SST Laravel to auto-inject environment variables, you can disable with the following option:
|
|
196
|
+
|
|
197
|
+
```js
|
|
198
|
+
config: {
|
|
199
|
+
environment: {
|
|
200
|
+
autoInject: false,
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
#### IAM Roles and Permissions
|
|
206
|
+
|
|
207
|
+
The IAM permissions for the linked resources are also automatically added to the ECS IAM Execution Role, meaning your application has access to all the linked resources.
|
|
208
|
+
|
|
209
|
+
### Other Configurations
|
|
210
|
+
|
|
211
|
+
You can configure the PHP version, custom environment variables and a custom deployment script.
|
|
212
|
+
|
|
213
|
+
```js
|
|
214
|
+
const app = new Laravel('MyLaravelApp', {
|
|
215
|
+
config: {
|
|
216
|
+
php: 8.4,
|
|
217
|
+
opcache: true,
|
|
218
|
+
deployment: {
|
|
219
|
+
script: './infra/deploy.sh'
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Custom deployment script example:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
#!/bin/sh
|
|
229
|
+
|
|
230
|
+
# Exit on error
|
|
231
|
+
set -e
|
|
232
|
+
|
|
233
|
+
echo "🚀 Running Deployment Script..."
|
|
234
|
+
|
|
235
|
+
cd "$APP_BASE_DIR"
|
|
236
|
+
|
|
237
|
+
echo "🚀 Running PHP Artisan Optimize..."
|
|
238
|
+
php artisan optimize
|
|
239
|
+
|
|
240
|
+
echo "🚀 Running Laravel Migrations..."
|
|
241
|
+
php artisan migrate --force
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## Debugging Containers
|
|
245
|
+
|
|
246
|
+
TODO: Add documentation on how to SSH to debug containers.
|
|
247
|
+
|
|
248
|
+
***
|
|
249
|
+
|
|
250
|
+
### Roadmap
|
|
251
|
+
|
|
252
|
+
* Custom CLI to facilitate accessing resources;
|
|
253
|
+
* Add support for Inertia SSR;
|
|
254
|
+
* Add support for Octane;
|
|
255
|
+
* Add support for Laravel Reverb;
|
|
256
|
+
* Dev mode;
|
|
257
|
+
|
|
258
|
+
## Security
|
|
259
|
+
|
|
260
|
+
If you discover any security related issues, please email security@kirschbaumdevelopment.com instead of using the issue tracker.
|
|
261
|
+
|
|
262
|
+
## Sponsorship
|
|
263
|
+
|
|
264
|
+
Development of this package is sponsored by Kirschbaum Development Group, a developer driven company focused on problem solving, team building, and community. Learn more [about us](https://kirschbaumdevelopment.com) or [join us](https://careers.kirschbaumdevelopment.com)!
|
|
265
|
+
|
|
266
|
+
## License
|
|
267
|
+
|
|
268
|
+
The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/command/with-contenv bash
|
|
2
|
+
|
|
3
|
+
# Exit on error
|
|
4
|
+
set -e
|
|
5
|
+
|
|
6
|
+
# Check to see if an Artisan file exists and assume it means Laravel is configured.
|
|
7
|
+
if [ -f "$APP_BASE_DIR/artisan" ]; then
|
|
8
|
+
echo "🚀 Starting Laravel Horizon..."
|
|
9
|
+
|
|
10
|
+
cd "$APP_BASE_DIR"
|
|
11
|
+
php "$APP_BASE_DIR/artisan" horizon
|
|
12
|
+
else
|
|
13
|
+
echo "👉 Skipping Laravel Horizon because we could not detect a Laravel install or it was specifically disabled..."
|
|
14
|
+
|
|
15
|
+
tail -f /dev/null
|
|
16
|
+
fi
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
longrun
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/command/with-contenv bash
|
|
2
|
+
|
|
3
|
+
# Exit on error
|
|
4
|
+
set -e
|
|
5
|
+
|
|
6
|
+
# Check to see if an Artisan file exists and assume it means Laravel is configured.
|
|
7
|
+
if [ -f "$APP_BASE_DIR/artisan" ]; then
|
|
8
|
+
echo "🚀 Starting Laravel Scheduler..."
|
|
9
|
+
|
|
10
|
+
cd "$APP_BASE_DIR"
|
|
11
|
+
php "$APP_BASE_DIR/artisan" schedule:work
|
|
12
|
+
else
|
|
13
|
+
echo "👉 Skipping Laravel Scheduler because we could not detect a Laravel install or it was specifically disabled..."
|
|
14
|
+
|
|
15
|
+
tail -f /dev/null
|
|
16
|
+
fi
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
longrun
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
##############
|
|
5
|
+
# BASED ON https://github.com/serversideup/docker-php/blob/main/src/common/usr/local/bin/docker-php-serversideup-entrypoint
|
|
6
|
+
##############
|
|
7
|
+
|
|
8
|
+
# Initialize variables
|
|
9
|
+
DEFAULT_COMMAND="false"
|
|
10
|
+
S6_INITIALIZED="false"
|
|
11
|
+
|
|
12
|
+
echo "🔥🔥🔥 STARTING ENTRYPOINT SCRIPT 🔥🔥🔥"
|
|
13
|
+
|
|
14
|
+
# Enable debug mode if LOG_OUTPUT_LEVEL is set to "debug"
|
|
15
|
+
if [ "$LOG_OUTPUT_LEVEL" = "debug" ]; then
|
|
16
|
+
echo "🔥🔥🔥 DEBUG MODE has been set. Get ready for a ton of debug log output..."
|
|
17
|
+
set -x
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
# Check if the default command is being used
|
|
21
|
+
case "$1" in
|
|
22
|
+
"/init")
|
|
23
|
+
DEFAULT_COMMAND="true"
|
|
24
|
+
;;
|
|
25
|
+
"unitd")
|
|
26
|
+
if [ "$2" = "--no-daemon" ]; then
|
|
27
|
+
DEFAULT_COMMAND="true"
|
|
28
|
+
fi
|
|
29
|
+
;;
|
|
30
|
+
esac
|
|
31
|
+
|
|
32
|
+
# Check if S6 overlay is initialized
|
|
33
|
+
if [ -d "/etc/s6-overlay" ] && [ "$DEFAULT_COMMAND" = "true" ]; then
|
|
34
|
+
S6_INITIALIZED="true"
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
# Export variables
|
|
38
|
+
export DEFAULT_COMMAND
|
|
39
|
+
export S6_INITIALIZED
|
|
40
|
+
|
|
41
|
+
###############################################
|
|
42
|
+
# Usage: entrypoint.sh
|
|
43
|
+
###############################################
|
|
44
|
+
# This script is used to execute scripts from "/etc/entrypoint.d" and then
|
|
45
|
+
# execute the CMD passed in from the Dockerfile.
|
|
46
|
+
|
|
47
|
+
# Execute scripts from /etc/entrypoint.d/ in numeric order
|
|
48
|
+
find /etc/entrypoint.d/ -type f -name '*.sh' | sort -V | while IFS= read -r f; do
|
|
49
|
+
if [ -e "$f" ]; then
|
|
50
|
+
if [ "$LOG_OUTPUT_LEVEL" = "debug" ]; then
|
|
51
|
+
echo "Executing $f"
|
|
52
|
+
fi
|
|
53
|
+
if ! . "$f"; then
|
|
54
|
+
echo "Error executing $f" >&2
|
|
55
|
+
exit 1
|
|
56
|
+
fi
|
|
57
|
+
else
|
|
58
|
+
echo "Warning: $f not found" >&2
|
|
59
|
+
fi
|
|
60
|
+
done
|
|
61
|
+
|
|
62
|
+
# first arg is `-f` or `--some-option`
|
|
63
|
+
if [ "${1#-}" != "$1" ]; then
|
|
64
|
+
set -- php "$@"
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# Some scripts may need to change the CMD based on the log level. If this file is set, execute the contents of that file instead of the Dockerfile CMD.
|
|
68
|
+
if [ -f /tmp/docker_cmd_override ]; then
|
|
69
|
+
docker_cmd_override=$(cat /tmp/docker_cmd_override)
|
|
70
|
+
rm /tmp/docker_cmd_override
|
|
71
|
+
set -- $docker_cmd_override # Perform word splitting by not quoting the commands
|
|
72
|
+
exec "$@"
|
|
73
|
+
else
|
|
74
|
+
# Execute the CMD passed in from the Dockerfile
|
|
75
|
+
exec "$@"
|
|
76
|
+
fi
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -oue
|
|
3
|
+
|
|
4
|
+
######################
|
|
5
|
+
# Usage: s6-install.sh
|
|
6
|
+
######################
|
|
7
|
+
|
|
8
|
+
# BASED ON https://github.com/serversideup/docker-php/blob/main/src/s6/usr/local/bin/docker-php-serversideup-s6-install
|
|
9
|
+
# This script is used to install S6 Overlay. It is intended to be used during the build process only.
|
|
10
|
+
# Be sure to set the S6_SRC_URL, S6_SRC_DEP, and S6_DIR environment variables before running this script.
|
|
11
|
+
|
|
12
|
+
S6_VERSION=v3.2.0.2
|
|
13
|
+
mkdir -p $S6_DIR
|
|
14
|
+
export SYS_ARCH=$(uname -m)
|
|
15
|
+
case "$SYS_ARCH" in
|
|
16
|
+
aarch64 ) export S6_ARCH='aarch64' ;;
|
|
17
|
+
arm64 ) export S6_ARCH='aarch64' ;;
|
|
18
|
+
armhf ) export S6_ARCH='armhf' ;;
|
|
19
|
+
arm* ) export S6_ARCH='arm' ;;
|
|
20
|
+
i4* ) export S6_ARCH='i486' ;;
|
|
21
|
+
i6* ) export S6_ARCH='i686' ;;
|
|
22
|
+
s390* ) export S6_ARCH='s390x' ;;
|
|
23
|
+
* ) export S6_ARCH='x86_64' ;;
|
|
24
|
+
esac
|
|
25
|
+
|
|
26
|
+
untar() {
|
|
27
|
+
echo "⏬ Downloading $1"
|
|
28
|
+
curl -L $1 -o - | tar Jxp -C $S6_DIR
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
echo "⬇️ Downloading s6 overlay:${S6_ARCH}-${S6_VERSION} for ${SYS_ARCH}"
|
|
32
|
+
untar ${S6_SRC_URL}/${S6_VERSION}/s6-overlay-noarch.tar.xz
|
|
33
|
+
untar ${S6_SRC_URL}/${S6_VERSION}/s6-overlay-${S6_ARCH}.tar.xz
|
|
Binary file
|
package/laravel-sst.ts
ADDED
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
/// <reference path="./../../.sst/platform/config.d.ts" />
|
|
2
|
+
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
import { Component } from "../../.sst/platform/src/components/component.js";
|
|
6
|
+
import { FunctionArgs } from "../../.sst/platform/src/components/aws/function.js";;
|
|
7
|
+
import { ComponentResourceOptions, Output, all, output } from "../../.sst/platform/node_modules/@pulumi/pulumi/index.js";
|
|
8
|
+
import { Input } from "../../.sst/platform/src/components/input.js";
|
|
9
|
+
import { Link } from "../../.sst/platform/src/components/link.js";
|
|
10
|
+
import { ClusterArgs } from "../../.sst/platform/src/components/aws/cluster.js";
|
|
11
|
+
import { ServiceArgs } from "../../.sst/platform/src/components/aws/service.js";
|
|
12
|
+
import { Dns } from "../../.sst/platform/src/components/dns.js";
|
|
13
|
+
import { Postgres } from "../../.sst/platform/src/components/aws/postgres.js";
|
|
14
|
+
import { Redis } from "../../.sst/platform/src/components/aws/redis.js";
|
|
15
|
+
import { Email } from "../../.sst/platform/src/components/aws/email.js";
|
|
16
|
+
import { applyLinkedResourcesEnv, EnvCallback, EnvCallbacks } from "./src/laravel-env.js";
|
|
17
|
+
|
|
18
|
+
// duplicate from cluster.ts
|
|
19
|
+
type Port = `${number}/${"http" | "https" | "tcp" | "udp" | "tcp_udp" | "tls"}`;
|
|
20
|
+
|
|
21
|
+
type Ports = {
|
|
22
|
+
listen: Port,
|
|
23
|
+
forward: Port
|
|
24
|
+
}[];
|
|
25
|
+
|
|
26
|
+
enum ImageType {
|
|
27
|
+
Web = 'web',
|
|
28
|
+
Worker = 'worker',
|
|
29
|
+
Cli = 'cli',
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface LaravelWebArgs {
|
|
33
|
+
/**
|
|
34
|
+
* Domain for the web layer.
|
|
35
|
+
*/
|
|
36
|
+
domain?: Input<
|
|
37
|
+
string
|
|
38
|
+
| {
|
|
39
|
+
name: Input<string>;
|
|
40
|
+
cert?: Input<string>;
|
|
41
|
+
dns?: Input<false | (Dns & {})>;
|
|
42
|
+
}
|
|
43
|
+
>;
|
|
44
|
+
|
|
45
|
+
loadBalancer?: ServiceArgs["loadBalancer"];
|
|
46
|
+
image?: ServiceArgs["image"];
|
|
47
|
+
scaling?: ServiceArgs["scaling"];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface LaravelWorkerConfig {
|
|
51
|
+
name?: Input<string>;
|
|
52
|
+
link?: ServiceArgs["link"];
|
|
53
|
+
scaling?: ServiceArgs["scaling"];
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Running horizon?
|
|
57
|
+
*/
|
|
58
|
+
horizon?: Input<boolean>;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Running scheduler?
|
|
62
|
+
*/
|
|
63
|
+
scheduler?: Input<boolean>;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Multiple tasks can be run in the worker.
|
|
67
|
+
*/
|
|
68
|
+
tasks?: Input<{
|
|
69
|
+
[key: string]: Input<{
|
|
70
|
+
command: Input<string>;
|
|
71
|
+
dependencies?: Input<string[]>;
|
|
72
|
+
}>
|
|
73
|
+
}>
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface LaravelArgs extends ClusterArgs {
|
|
77
|
+
// dev?: false | DevArgs["dev"];
|
|
78
|
+
path?: Input<string>;
|
|
79
|
+
link?: Array<
|
|
80
|
+
| any
|
|
81
|
+
| {
|
|
82
|
+
resource: any;
|
|
83
|
+
environment?: EnvCallback;
|
|
84
|
+
}
|
|
85
|
+
>;
|
|
86
|
+
|
|
87
|
+
permissions?: Array<{
|
|
88
|
+
actions: string[];
|
|
89
|
+
resources: string[];
|
|
90
|
+
}>;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* If enabled, a container will be created to handle HTTP traffic.
|
|
94
|
+
*/
|
|
95
|
+
web?: LaravelWebArgs;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Multiple workers settings.
|
|
99
|
+
*/
|
|
100
|
+
workers?: LaravelWorkerConfig[];
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Config settings.
|
|
104
|
+
*/
|
|
105
|
+
config?: {
|
|
106
|
+
php?: Input<Number>;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* PHP Opcache should be enabled?
|
|
110
|
+
*
|
|
111
|
+
* @default `true`
|
|
112
|
+
*/
|
|
113
|
+
opcache?: Input<boolean>;
|
|
114
|
+
environment?: {
|
|
115
|
+
/**
|
|
116
|
+
* Use this option if you want to import an .env file during build. By default, SST Laravel won't use your .env file since that might be the wrong file when deploying from your local machine.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```js
|
|
120
|
+
* # Use use a fila named .env.$stage as your .env file
|
|
121
|
+
* environment: {
|
|
122
|
+
* file: `.env.${$app.stage}`,
|
|
123
|
+
* }
|
|
124
|
+
* OR
|
|
125
|
+
* environment: {
|
|
126
|
+
* file: `.env`,
|
|
127
|
+
* }
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
file?: Input<string>,
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Set this to false in case you don't want to auto inject environment variables from your linked resources.
|
|
134
|
+
*
|
|
135
|
+
* @default `true`
|
|
136
|
+
*/
|
|
137
|
+
autoInject?: Input<boolean>,
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Custom environment variables that will be automatically injected into your application.
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```js
|
|
144
|
+
* environment: {
|
|
145
|
+
* vars: {
|
|
146
|
+
* SESSION_DRIVER: 'redis',
|
|
147
|
+
* QUEUE_CONNECTION: 'redis',
|
|
148
|
+
* }
|
|
149
|
+
* }
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
vars?: FunctionArgs["environment"],
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Custom deployment configurations.
|
|
157
|
+
*/
|
|
158
|
+
deployment?: {
|
|
159
|
+
migrate?: Input<boolean>;
|
|
160
|
+
optimize?: Input<boolean>;
|
|
161
|
+
script?: Input<string>;
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export class Laravel extends Component {
|
|
167
|
+
constructor(
|
|
168
|
+
name: string,
|
|
169
|
+
args: LaravelArgs,
|
|
170
|
+
opts: ComponentResourceOptions = {},
|
|
171
|
+
) {
|
|
172
|
+
super(__pulumiType, name, args, opts);
|
|
173
|
+
|
|
174
|
+
args.config = args.config ?? {};
|
|
175
|
+
const sitePath = args.path ?? '.';
|
|
176
|
+
const absSitePath = path.resolve(sitePath.toString());
|
|
177
|
+
// TODO: We need to update sst-laravel to whatever the real package name will be.
|
|
178
|
+
const nodeModulePath = path.resolve(__dirname, '../../node_modules/sst-laravel');
|
|
179
|
+
|
|
180
|
+
// Determine the path where our plugin will save build files. SST sets __dirname to the .sst/platform directory.
|
|
181
|
+
const pluginBuildPath = path.resolve(__dirname, '../laravel');
|
|
182
|
+
|
|
183
|
+
prepareEnvironmentFile();
|
|
184
|
+
prepareDeploymentScript();
|
|
185
|
+
|
|
186
|
+
const cluster = new sst.aws.Cluster(`${name}-Cluster`, {
|
|
187
|
+
vpc: args.vpc
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
if (args.web) {
|
|
191
|
+
addWebService();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (args.workers) {
|
|
195
|
+
addWorkerServices();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function addWebService() {
|
|
199
|
+
const envVariables = getEnvironmentVariables();
|
|
200
|
+
|
|
201
|
+
const webService = new sst.aws.Service(`${name}-Web`, {
|
|
202
|
+
cluster,
|
|
203
|
+
link: getLinks(),
|
|
204
|
+
permissions: args.permissions,
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Image passed or use our default provided image.
|
|
208
|
+
*/
|
|
209
|
+
image: getImage(args.web?.image, ImageType.Web),
|
|
210
|
+
environment: envVariables,
|
|
211
|
+
scaling: args.web?.scaling,
|
|
212
|
+
|
|
213
|
+
loadBalancer: args.web && args.web.loadBalancer ? args.web.loadBalancer : {
|
|
214
|
+
domain: args.web?.domain,
|
|
215
|
+
ports: getDefaultPublicPorts(),
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
dev: {
|
|
219
|
+
command: `php ${sitePath}/artisan serve`,
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function createWorkerTasks(workerConfig: LaravelWorkerConfig, workerBuildPath: string) {
|
|
225
|
+
const s6RcDPath = path.resolve(workerBuildPath, 'etc/s6-overlay/s6-rc.d');
|
|
226
|
+
const s6UserContentsPath = path.resolve(s6RcDPath, 'user/contents.d');
|
|
227
|
+
|
|
228
|
+
fs.mkdirSync(s6UserContentsPath, { recursive: true });
|
|
229
|
+
|
|
230
|
+
const tasks: Record<string, { command: string; dependencies?: string[] }> = {
|
|
231
|
+
...((workerConfig.tasks as any) ?? {}),
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
if (workerConfig.horizon) {
|
|
235
|
+
tasks['laravel-horizon'] = {
|
|
236
|
+
command: 'php artisan horizon',
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (workerConfig.scheduler) {
|
|
241
|
+
tasks['laravel-scheduler'] = {
|
|
242
|
+
command: 'php artisan schedule:work',
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
Object.entries(tasks).forEach(([taskName, config]) => {
|
|
247
|
+
const tasksDir = path.resolve(s6RcDPath, `${taskName}`);
|
|
248
|
+
fs.mkdirSync(tasksDir, { recursive: true });
|
|
249
|
+
|
|
250
|
+
const scriptSrcPath = path.join(tasksDir, 'script');
|
|
251
|
+
|
|
252
|
+
fs.writeFileSync(scriptSrcPath, `#!/command/with-contenv bash\ncd /var/www/html\n${config.command}`, { mode: 0o777 });
|
|
253
|
+
fs.writeFileSync(path.join(tasksDir, 'run'), `#!/command/execlineb -P\n/etc/s6-overlay/s6-rc.d/${taskName}/script`, { mode: 0o777 });
|
|
254
|
+
fs.writeFileSync(path.join(tasksDir, 'type'), 'longrun');
|
|
255
|
+
fs.writeFileSync(path.join(tasksDir, 'dependencies'), (config.dependencies || []).join('\n'));
|
|
256
|
+
fs.writeFileSync(path.join(s6UserContentsPath, taskName), '');
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function createWorkerService(workerConfig: LaravelWorkerConfig, serviceName: string, workerBuildPath: string) {
|
|
261
|
+
createWorkerTasks(workerConfig, workerBuildPath);
|
|
262
|
+
|
|
263
|
+
const imgBuildArgs = {
|
|
264
|
+
'CONF_PATH': path.resolve(nodeModulePath, 'conf').replace(absSitePath, ''),
|
|
265
|
+
'CUSTOM_CONF_PATH': workerBuildPath.replace(absSitePath, ''),
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
return new sst.aws.Service(serviceName, {
|
|
269
|
+
cluster,
|
|
270
|
+
link: getLinks(),
|
|
271
|
+
permissions: args.permissions,
|
|
272
|
+
|
|
273
|
+
image: getImage(args.web?.image, ImageType.Worker, imgBuildArgs),
|
|
274
|
+
scaling: workerConfig.scaling,
|
|
275
|
+
environment: getEnvironmentVariables(),
|
|
276
|
+
|
|
277
|
+
dev: {
|
|
278
|
+
command: `php ${sitePath}/artisan horizon`,
|
|
279
|
+
},
|
|
280
|
+
|
|
281
|
+
transform: {
|
|
282
|
+
taskDefinition: (args) => {
|
|
283
|
+
args.containerDefinitions = (args.containerDefinitions as $util.Output<string>).apply(a => {
|
|
284
|
+
return JSON.stringify([{
|
|
285
|
+
...JSON.parse(a)[0],
|
|
286
|
+
linuxParameters: {
|
|
287
|
+
initProcessEnabled: false,
|
|
288
|
+
}
|
|
289
|
+
}]);
|
|
290
|
+
})
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}, {
|
|
294
|
+
dependsOn: [],
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function addWorkerServices() {
|
|
299
|
+
args.workers?.forEach((workerConfig, index) => {
|
|
300
|
+
const workerName = workerConfig.name || `worker-${index + 1}`;
|
|
301
|
+
const absWorkerBuildPath = path.resolve(pluginBuildPath, `worker-${workerName}`);
|
|
302
|
+
|
|
303
|
+
createWorkerService(workerConfig, `${name}-${workerName}`, absWorkerBuildPath);
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function getDefaultPublicPorts(): Ports {
|
|
308
|
+
let ports;
|
|
309
|
+
const forwardPort: Port = "8080/http";
|
|
310
|
+
const portHttp: Port = "80/http";
|
|
311
|
+
const portHttps: Port = "443/https";
|
|
312
|
+
|
|
313
|
+
if (args.web?.domain) {
|
|
314
|
+
ports = [
|
|
315
|
+
{ listen: portHttp, forward: forwardPort },
|
|
316
|
+
{ listen: portHttps, forward: forwardPort },
|
|
317
|
+
];
|
|
318
|
+
} else {
|
|
319
|
+
ports = [
|
|
320
|
+
{ listen: portHttp, forward: forwardPort },
|
|
321
|
+
];
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return ports;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// TODO: We have to test if it works when an image is provided in sst.config.js
|
|
328
|
+
function getImage(imgFromConfig: LaravelWebArgs["image"] | null | undefined, imgType: ImageType, extraArgs: object = {}) {
|
|
329
|
+
const img = imgFromConfig
|
|
330
|
+
? imgFromConfig
|
|
331
|
+
: getDefaultImage(imgType, extraArgs);
|
|
332
|
+
|
|
333
|
+
const context = typeof img === 'string'
|
|
334
|
+
? sitePath.toString()
|
|
335
|
+
: (img as { context: string }).context.toString();
|
|
336
|
+
|
|
337
|
+
const dockerfile = typeof img === 'string'
|
|
338
|
+
? 'Dockerfile'
|
|
339
|
+
: (img as { dockerfile: string }).dockerfile;
|
|
340
|
+
|
|
341
|
+
// add .sst/laravel to .dockerignore if not exist
|
|
342
|
+
const dockerIgnore = (() => {
|
|
343
|
+
let filePath = path.join(context, `${dockerfile}.dockerignore`);
|
|
344
|
+
if (fs.existsSync(filePath)) return filePath;
|
|
345
|
+
|
|
346
|
+
filePath = path.join(context, ".dockerignore");
|
|
347
|
+
if (fs.existsSync(filePath)) return filePath;
|
|
348
|
+
})();
|
|
349
|
+
|
|
350
|
+
const content = dockerIgnore ? fs.readFileSync(dockerIgnore).toString() : "";
|
|
351
|
+
|
|
352
|
+
const lines = content.split("\n");
|
|
353
|
+
|
|
354
|
+
// SST adds it later, so we need to add it here to ensure .sst/laravel is after it and is not ignored
|
|
355
|
+
if (dockerIgnore) {
|
|
356
|
+
if (!lines.find((line) => line === ".sst")) {
|
|
357
|
+
fs.writeFileSync(
|
|
358
|
+
dockerIgnore,
|
|
359
|
+
[...lines, "", "# sst", "!.sst/laravel"].join("\n"),
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (!lines.find((line) => line === "!.sst/laravel")) {
|
|
364
|
+
fs.writeFileSync(
|
|
365
|
+
dockerIgnore,
|
|
366
|
+
[...lines, "", "# sst-laravel", "!.sst/laravel"].join("\n"),
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return img;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function getDefaultImage(imageType: ImageType, extraArgs: object = {}) {
|
|
375
|
+
return {
|
|
376
|
+
context: sitePath,
|
|
377
|
+
dockerfile: path.resolve(nodeModulePath, `Dockerfile.${imageType}`).replace(absSitePath, '.'),
|
|
378
|
+
args: {
|
|
379
|
+
'PHP_VERSION': getPhpVersion().toString(),
|
|
380
|
+
'PHP_OPCACHE_ENABLE': args.config?.opcache? '1' : '0',
|
|
381
|
+
'AUTORUN_LARAVEL_MIGRATION': imageType === ImageType.Web ? 'true' : 'false',
|
|
382
|
+
'CONTAINER_TYPE': imageType,
|
|
383
|
+
stage: "deploy",
|
|
384
|
+
platform: "linux/amd64",
|
|
385
|
+
...extraArgs
|
|
386
|
+
},
|
|
387
|
+
};
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
function getPhpVersion() {
|
|
391
|
+
return args.config?.php ?? 8.4;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function getEnvironmentVariables() {
|
|
395
|
+
const env = args.config?.environment?.vars || {};
|
|
396
|
+
|
|
397
|
+
if (args.web?.domain) {
|
|
398
|
+
if (typeof args.web.domain === 'string') {
|
|
399
|
+
(env as any)['APP_URL'] = args.web.domain;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return env;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function applyLinkedResourcesToEnvironment() {
|
|
407
|
+
const links = (args.link || []);
|
|
408
|
+
const resources: any[] = [];
|
|
409
|
+
const customEnv: Record<string, string | Output<string>> = {};
|
|
410
|
+
|
|
411
|
+
links.forEach(link => {
|
|
412
|
+
if (link && typeof link === 'object' && 'resource' in link) {
|
|
413
|
+
// Link is an object with resource and optional envCallback
|
|
414
|
+
resources.push(link.resource);
|
|
415
|
+
|
|
416
|
+
// If there's an envCallback, call it and merge the result
|
|
417
|
+
if (link.envCallback) {
|
|
418
|
+
const callbackResult = link.envCallback(link.resource);
|
|
419
|
+
Object.assign(customEnv, callbackResult);
|
|
420
|
+
}
|
|
421
|
+
} else {
|
|
422
|
+
// Link is just a resource
|
|
423
|
+
resources.push(link);
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
// Apply default environment variables for all resources
|
|
428
|
+
if (!args.config) args.config = {};
|
|
429
|
+
if (!args.config.environment) args.config.environment = {};
|
|
430
|
+
|
|
431
|
+
const resourcesEnvVars = {
|
|
432
|
+
...applyLinkedResourcesEnv(resources),
|
|
433
|
+
...customEnv,
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
// TODO: Write resourcesEnvVars to the .env file
|
|
437
|
+
// TODO: Add proper types
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Return the links as an array of resources in the original SST format.
|
|
442
|
+
*/
|
|
443
|
+
function getLinks(): any[] {
|
|
444
|
+
return (args.link || []).map(link => {
|
|
445
|
+
if (link && typeof link === 'object' && 'resource' in link) {
|
|
446
|
+
return link.resource;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return link;
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function prepareEnvironmentFile() {
|
|
454
|
+
const envFile = args.config?.environment?.file as string | undefined;
|
|
455
|
+
|
|
456
|
+
if (! envFile) {
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const envDir = path.resolve(pluginBuildPath, 'deploy');
|
|
461
|
+
const dst = path.resolve(envDir, '.env');
|
|
462
|
+
const src = path.resolve(absSitePath, envFile);
|
|
463
|
+
|
|
464
|
+
if (fs.existsSync(src)) {
|
|
465
|
+
fs.copyFileSync(src, dst);
|
|
466
|
+
fs.chmodSync(dst, 0o755);
|
|
467
|
+
} else {
|
|
468
|
+
fs.writeFileSync(dst, '');
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (! args.config?.environment?.autoInject === false) {
|
|
472
|
+
applyLinkedResourcesToEnvironment();
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function prepareDeploymentScript() {
|
|
477
|
+
const deployDir = path.resolve(pluginBuildPath, 'deploy');
|
|
478
|
+
const dst = path.resolve(deployDir, '60-deploy.sh');
|
|
479
|
+
|
|
480
|
+
fs.mkdirSync(deployDir, { recursive: true });
|
|
481
|
+
|
|
482
|
+
const script = args.config?.deployment?.script as string | undefined;
|
|
483
|
+
if (script) {
|
|
484
|
+
const src = path.resolve(absSitePath, script);
|
|
485
|
+
if (fs.existsSync(src)) {
|
|
486
|
+
fs.copyFileSync(src, dst);
|
|
487
|
+
fs.chmodSync(dst, 0o755);
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
fs.writeFileSync(dst, "#!/bin/sh\nexit 0\n");
|
|
493
|
+
fs.chmodSync(dst, 0o755);
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const __pulumiType = "sst:aws:Laravel";
|
|
499
|
+
// @ts-expect-error
|
|
500
|
+
Laravel.__pulumiType = __pulumiType;
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kirschbaum-development/sst-laravel",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "An unofficial extension of SST to deploy containerized Laravel applications to AWS Fargate.",
|
|
5
|
+
"main": "laravel-sst.ts",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/kirschbaumdevelopment/sst-laravel.git"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"laravel",
|
|
12
|
+
"sst",
|
|
13
|
+
"aws",
|
|
14
|
+
"serverless"
|
|
15
|
+
],
|
|
16
|
+
"author": {
|
|
17
|
+
"name": "Luis Dalmolin",
|
|
18
|
+
"email": "luis@kirschbaumdevelopment.com"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/kirschbaum-development/sst-laravel/issues"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/kirschbaum-development/sst-laravel#readme",
|
|
25
|
+
"dependencies": {}
|
|
26
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { Email } from "../../../.sst/platform/src/components/aws/email.js";
|
|
2
|
+
import { Mysql } from "../../../.sst/platform/src/components/aws/mysql.js";
|
|
3
|
+
import { Postgres } from "../../../.sst/platform/src/components/aws/postgres.js";
|
|
4
|
+
import { Redis } from "../../../.sst/platform/src/components/aws/redis.js";
|
|
5
|
+
import { Output } from "../../../.sst/platform/node_modules/@pulumi/pulumi/index.js";
|
|
6
|
+
import * as pulumiAws from "../../../.sst/platform/node_modules/@pulumi/aws";
|
|
7
|
+
import { Queue } from "../../../.sst/platform/src/components/aws/queue.js";
|
|
8
|
+
import { Aurora } from "../../../.sst/platform/src/components/aws/aurora.js";
|
|
9
|
+
import { Bucket } from "../../../.sst/platform/src/components/aws/bucket.js";
|
|
10
|
+
|
|
11
|
+
type EnvType = Record<string, string | Output<string>>|Record<string, string | Output<string | undefined> | undefined>;
|
|
12
|
+
type Database = Postgres | Mysql | Aurora | pulumiAws.rds.Instance;
|
|
13
|
+
type LinkSupportedTypes = Database | Email | Queue | Redis | Bucket;
|
|
14
|
+
|
|
15
|
+
export type EnvCallback = (resource: any) => EnvType;
|
|
16
|
+
export type EnvCallbacks = {
|
|
17
|
+
postgres?: EnvCallback;
|
|
18
|
+
mysql?: EnvCallback;
|
|
19
|
+
redis?: EnvCallback;
|
|
20
|
+
email?: EnvCallback;
|
|
21
|
+
queue?: EnvCallback;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export function applyLinkedResourcesEnv(links: LinkSupportedTypes[], callbacks?: EnvCallbacks): EnvType {
|
|
25
|
+
let environment: EnvType = {};
|
|
26
|
+
|
|
27
|
+
links.forEach((link: LinkSupportedTypes) => {
|
|
28
|
+
if (link instanceof Postgres) {
|
|
29
|
+
const defaultEnv = applyDatabaseEnv(link);
|
|
30
|
+
|
|
31
|
+
environment = {
|
|
32
|
+
...environment,
|
|
33
|
+
...defaultEnv,
|
|
34
|
+
...(callbacks?.postgres ? callbacks.postgres(link) : {}),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (link instanceof Redis) {
|
|
39
|
+
const defaultEnv = applyRedisEnv(link);
|
|
40
|
+
|
|
41
|
+
environment = {
|
|
42
|
+
...environment,
|
|
43
|
+
...defaultEnv,
|
|
44
|
+
...(callbacks?.redis ? callbacks.redis(link) : {}),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (link instanceof Email) {
|
|
49
|
+
const defaultEnv = applyEmailEnv(link);
|
|
50
|
+
|
|
51
|
+
environment = {
|
|
52
|
+
...environment,
|
|
53
|
+
...defaultEnv,
|
|
54
|
+
...(callbacks?.email ? callbacks.email(link) : {}),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (link instanceof Queue) {
|
|
59
|
+
const defaultEnv = applyQueueEnv(link);
|
|
60
|
+
|
|
61
|
+
environment = {
|
|
62
|
+
...environment,
|
|
63
|
+
...defaultEnv,
|
|
64
|
+
...(callbacks?.queue ? callbacks.queue(link) : {}),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (link instanceof Bucket) {
|
|
69
|
+
const defaultEnv = applyBucketEnv(link);
|
|
70
|
+
|
|
71
|
+
environment = {
|
|
72
|
+
...environment,
|
|
73
|
+
...defaultEnv,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return environment;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function applyDatabaseEnv(database: Database, callbacks?: EnvCallbacks): EnvType {
|
|
82
|
+
let port: number;
|
|
83
|
+
database.port.apply(value => port = value);
|
|
84
|
+
|
|
85
|
+
if (database instanceof Postgres || (database instanceof Aurora && port === 5432)) {
|
|
86
|
+
return applyPostgresEnv(database);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (database instanceof Mysql || (database instanceof Aurora && port === 3306) || database instanceof pulumiAws.rds.Instance) {
|
|
90
|
+
return applyMySqlEnv(database);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function applyPostgresEnv(database: Postgres|Aurora): EnvType {
|
|
97
|
+
const port: Output<number> = database.port;
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
DB_CONNECTION: 'pgsql',
|
|
101
|
+
DB_HOST: database.host,
|
|
102
|
+
DB_DATABASE: database.database,
|
|
103
|
+
DB_USERNAME: database.username,
|
|
104
|
+
DB_PASSWORD: database.password,
|
|
105
|
+
DB_PORT: port.apply(port => port.toString()),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function applyMySqlEnv(database: Mysql|Aurora|pulumiAws.rds.Instance): EnvType {
|
|
110
|
+
const port: Output<number> = database.port;
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
DB_CONNECTION: 'mysql',
|
|
114
|
+
DB_HOST: database instanceof Aurora || database instanceof Mysql ? database.host : database.endpoint,
|
|
115
|
+
DB_DATABASE: database instanceof Aurora || database instanceof Mysql ? database.database : database.dbName,
|
|
116
|
+
DB_USERNAME: database.username,
|
|
117
|
+
DB_PASSWORD: database.password,
|
|
118
|
+
DB_PORT: port.apply(port => port.toString()),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function applyRedisEnv(database: Redis): EnvType {
|
|
123
|
+
// TODO: Check if when encryption at rest is disabled, TLS is not required/throw errors
|
|
124
|
+
return {
|
|
125
|
+
REDIS_HOST: database.host.apply(host => host ? `tls://${host}` : ''),
|
|
126
|
+
REDIS_PORT: database.port.apply(port => port.toString()),
|
|
127
|
+
REDIS_PASSWORD: database.password,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// TODO
|
|
132
|
+
export function applyEmailEnv(mail: Email): EnvType {
|
|
133
|
+
return {
|
|
134
|
+
MAIL_MAILER: 'ses',
|
|
135
|
+
// MAIL_FROM_ADDRESS: link.sender,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// TODO
|
|
140
|
+
export function applyQueueEnv(queue: Queue): EnvType {
|
|
141
|
+
const queueUrl: Output<string> = queue.url;
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
SQS_QUEUE: queue.url,
|
|
145
|
+
// MAIL_FROM_ADDRESS: link.sender,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function applyBucketEnv(bucket: Bucket): EnvType {
|
|
150
|
+
return {
|
|
151
|
+
FILESYSTEM_DISK: 's3',
|
|
152
|
+
AWS_BUCKET: bucket.name,
|
|
153
|
+
};
|
|
154
|
+
}
|